ferrule

style and naming

α1

style and naming guidelines


Naming Conventions

Types, Domains, Roles

Use PascalCase:

type HttpClient = { ... };
domain IoError { ... }
role Hashable;

Variables, Functions, Fields

Use camelCase:

const requestId = generateId();
function readFile(path: Path) -> Bytes { ... }
type User = { firstName: String, lastName: String };

Constants

Use camelCase or SCREAMING_SNAKE_CASE consistently project-wide:

const maxRetries = 3;
const DEFAULT_TIMEOUT = Duration.seconds(30);

Packages

Use lowercase dot-separated names:

package net.http;
package data.json;
package my.app.server;

Formatting

Indentation

Use 2 spaces (no tabs):

function process(x: i32) -> i32 {
  if x > 0 {
    return x * 2;
  } else {
    return 0;
  }
}

Line Length

Prefer lines under 100 characters. Break long expressions:

const result = very_long_function_name(
  first_argument,
  second_argument,
  third_argument
);

Braces

Opening brace on same line:

function foo() -> Unit {
  // ...
}

if condition {
  // ...
} else {
  // ...
}

Code Organization

Avoid Deep Nesting

Use early returns and ensure:

// PREFER
function process(input: Input) -> Output error ProcessError {
  ensure input.valid === true else err InvalidInput { ... };
  ensure input.size < MAX_SIZE else err TooLarge { ... };
  
  // main logic at low nesting level
  return ok transform(input);
}

// AVOID
function process(input: Input) -> Output error ProcessError {
  if input.valid === true {
    if input.size < MAX_SIZE {
      // deeply nested
      return ok transform(input);
    } else {
      return err TooLarge { ... };
    }
  } else {
    return err InvalidInput { ... };
  }
}

Small, Focused Functions

Break large functions into smaller helpers:

function processRequest(req: Request) -> Response error AppError effects [fs, net] {
  const config = check loadConfig(fs);
  const data = check fetchData(req.url, net);
  const result = check transform(data, config);
  return ok formatResponse(result);
}

Pure Helpers

Keep effectful edges thin; push pure logic into helpers:

// pure: easy to test, easy to reason about
function validatePort(n: u32) -> Bool {
  return n >= 1 && n <= 65535;
}

// effectful: thin wrapper
function readPort(path: Path, cap fs: Fs) -> Port error ConfigError effects [fs] {
  const text = check fs.read_all_text(path);
  const n = check number.parse_u32(text);
  ensure validatePort(n) === true else err InvalidPort { value: n };
  return ok Port(n);
}

Comments

Principles

  • comments must add value
  • prefer clear code over explanatory comments
  • don't write filler comments

Good Comments

// handles the edge case where the buffer wraps around
const effective_len = if end > start { end - start } else { capacity - start + end };

// SAFETY: pointer is valid because we just allocated it above
const view = view.from_raw_unchecked(ptr, len);

Bad Comments

// increment counter
counter = counter + 1;

// check if x is greater than 10
if x > 10 { ... }

Documentation Comments

/// parses a port number from a string
/// 
/// returns ParseError if the string is not a valid number
/// or if the number is outside the valid port range (1-65535)
function parsePort(s: String) -> Port error ParseError { ... }

Security Guidelines

Input Validation

Validate at boundaries:

function handleRequest(raw: Bytes) -> Response error AppError {
  const request = check parseRequest(raw);  // validate structure
  ensure request.size < MAX_REQUEST_SIZE else err TooLarge { ... };
  ensure isAllowedPath(request.path) === true else err Forbidden { ... };
  // ...
}

No Hardcoded Secrets

Pass secrets via capabilities or configuration:

// WRONG
const API_KEY = "sk-12345...";

// CORRECT
function callApi(cap secrets: Secrets) -> Response effects [net] {
  const key = secrets.get("API_KEY");
  // ...
}

Use Refinements for Invariants

type Port = u16 where self >= 1 && self <= 65535;
type Email = String where text.contains(self, "@");

Secure Memory Handling

function processSecret(secret: View<u8>) -> Hash effects [alloc] {
  const hash = crypto.hash(secret);
  mem.secure_zero(secret);  // not optimized away
  return hash;
}

Summary

ElementConvention
types, domains, rolesPascalCase
variables, functions, fieldscamelCase
constantscamelCase or SCREAMING_SNAKE_CASE
packageslowercase.dot.separated
indentation2 spaces
bracessame line

On this page