Word Operators: and, or, not, xor

Decision: Logical operators are English words. Bitwise operators are symbols.

Reason: The single most common C mistake is writing & when you meant &&, or | when you meant ||. They look nearly identical and silently produce wrong results. Word operators for logic, symbol operators for bitwise — makes the distinction impossible to miss.

  -- logical: words
if x > 0 and y > 0 {
    proceed();
}

if not valid or count = 0 {
    return error.Invalid;
}

if flagA xor flagB {       -- exactly one is true
    handleExclusive();
}

-- bitwise: symbols (distinct, cannot be confused)
var flags  = a & b;        -- AND
var mask   = a | b;        -- OR
var inv    = ~a;            -- NOT
var mixed  = a ^ b;        -- XOR
var shifted = a << 3;
  

:= For Reassignment, = For Equality

Decision: := is the only reassignment operator. = in expression position is always equality comparison. = in declaration position is initialization.

Reason: The = vs == confusion has caused countless bugs in C-family languages. Pascal’s solution — use := for assignment — eliminates the ambiguity completely. There is no == in Violet. = always means “equal” in expressions, which is both intuitive and unambiguous.

  var x: i32 = 5;      -- = in declaration: initialization
x := 10;             -- := always: reassignment
x += 1;              -- compound: += is fine, := is base

if x = 10 {          -- = in expression: equality check
    debug.print("ten\n");
}

-- the classic C bug is impossible:
-- if (x = 10) { ... }  -- in C: assigns 10 to x, always true
-- if x = 10 { ... }    -- in Violet: checks if x equals 10
  

case — Pattern Matching

Decision: case is Violet’s pattern matching keyword, replacing match (Rust) and switch (C).

Reason: case is Pascal’s keyword for this construct. match carries Rust connotations. switch implies fall-through (C’s switch falls through by default, a historical mistake). case is neutral, readable, and matches the Pascal lineage.

  type Message = union {
    Text    { content: []const u8 },
    Image   { url: []const u8, width: u32, height: u32 },
    System  { code: u32 },
    Disconnect,
};

fn handle(msg: Message): !void {
    case msg {
        .Text { content }           => try sendText(content),
        .Image { url, width, height } => try sendImage(url, width, height),
        .System { code } if code = 0 => debug.print("heartbeat\n"),
        .System { code }            => try handleSystemMsg(code),
        .Disconnect                 => return error.Disconnected,
    }
}

-- case as expression
var priority = case msg {
    .System { code } => code,
    .Disconnect      => 255,
    _                => 0,
};
  

repeat...until — Post-Condition Loop

Decision: repeat { ... } until condition is a first-class loop construct. The body executes at least once; the condition is checked after.

Reason: The post-condition loop expresses “do this, then check” naturally — a common pattern for input validation, retry logic, and event draining. Expressing it as loop { ...; if cond { break; } } obscures intent. Pascal has had this construct for 50 years.

  -- input validation: must run at least once
var input: []const u8 = "";
repeat {
    input = try readLine();
} until input.len > 0 and input.len < 100;

-- retry with backoff
var attempts: u32 = 0;
var result: ?Connection = .None;
repeat {
    attempts += 1;
    result = tryConnect(host) catch .None;
    if result = .None {
        try sleep(Duration::millis(100 * attempts));
    }
} until result != .None or attempts = 5;

-- drain a queue
repeat {
    var item = queue.tryGet() orelse break;
    process(item);
} until queue.isEmpty();
  

nil — Raw Pointer Null

Decision: nil is the null value for raw pointer types only. Optional types use .None.

Reason: Pascal uses nil for pointer null. In Violet, the semantic distinction between “a raw pointer that hasn’t been set” (nil) and “an optional value that’s absent” (.None) is meaningful. Conflating them (as C does with NULL) hides intent. Optionals and raw pointers are different concepts.

  -- raw pointers: nil
var p: *i32 = nil;
if p = nil {
    #panic("expected non-null pointer");
}

-- optionals: .None (never nil)
var maybe: ?i32 = .None;
var maybe: ?i32 = nil;    -- compile error: use .None for optionals

-- at FFI boundaries: nil maps to C NULL naturally
extern fn sqlite3_open(path: [*c]const u8, db: *[*c]anyopaque): i32;

unsafe {
    var db: [*c]anyopaque = nil;
    _ = sqlite3_open("mydb.sqlite", &db);
}
  

type Name = ... — Universal Type Declaration

Decision: All named types are declared with type Name = kind { ... }. No separate struct Name, enum Name, class Name keywords.

Reason: Pascal’s type section makes all type declarations syntactically uniform. In C, struct, enum, union, typedef are all different syntactic forms. Violet’s uniformity means type declarations are always recognisable by the type keyword, and the kind (struct, enum, union, error) appears after =.

  type Point    = struct { x: f32, y: f32 };
type Color    = enum   { Red, Green, Blue };
type Shape    = union  { Circle { r: f32 }, Rect { w: f32, h: f32 } };
type NetError = error  { Timeout, Refused { code: i32 } };
type Meters   = f32;      -- type alias

-- every declaration: type Name = kind
-- no: struct Point { ... }     (C style, rejected)
-- no: enum Color { ... }       (C++ style, rejected)
  

var and const — Explicit Mutability

Decision: var declares a mutable binding. const declares an immutable binding. Both are from Pascal.

Reason: Making mutability explicit at the declaration site is clearer than Rust’s let/let mut distinction (which is easy to miss) and better than C’s implicit mutability (everything is mutable by default). const in Violet means “this binding cannot be rebound” — it’s stronger than const in C.

  const MAX: usize = 1024;         -- immutable, compile-time constant
var count: i32 = 0;              -- mutable

count := count + 1;              -- valid
MAX := MAX + 1;                  -- compile error: MAX is const

-- const in function parameters
fn process(*const self, data: []const u8): void {
    -- self cannot be mutated
    -- data bytes cannot be mutated through this slice
}

-- struct fields can also have readonly visibility
type Config = struct {
    pub readonly port: u16,      -- readable outside, writable inside only
    host: []const u8,
};
  

packed struct — Struct-Side Modifier

Decision: packed is a modifier on the struct kind, not on the type declaration: type Flags = packed struct { ... }.

Reason: packed describes how the struct lays out its fields — it’s a property of the struct, not of the name being declared. “This type is a packed struct” vs “this packed type is a struct.” The modifier belongs where the struct is defined.

  -- correct: packed modifies the struct
type Flags = packed struct {
    active:   u1,
    mode:     u3,
    priority: u4,
};

-- also: extern modifies layout, same position
type CHeader = extern struct {
    magic:   u32,
    version: u16,
    flags:   u16,
};

-- combinable
type WirePacket = extern packed struct {
    type_:   u4,
    flags:   u4,
    length:  u8,
};

#assert(#sizeOf(Flags)      = 1);
#assert(#sizeOf(CHeader)    = 8);
#assert(#sizeOf(WirePacket) = 2);
  

then — If As Expression

Decision: Single-expression conditionals use then: if cond then a else b. Multi-line conditionals use blocks.

Reason: Rust uses the same if for both statements and expressions, disambiguated by presence/absence of semicolons. This is subtle and error-prone. Pascal’s then keyword clearly marks “this is the expression form of if.” Using blocks for statements and then for expressions creates zero ambiguity.

  -- expression form: then...else
var abs    = if x >= 0 then x else -x;
var label  = if count = 1 then "item" else "items";
var capped = if val > max then max else val;

-- chained (still expression)
var grade = if score >= 90 then "A"
            else if score >= 80 then "B"
            else if score >= 70 then "C"
            else "F";

-- statement form: blocks (no then)
if condition {
    doThisThing();
    doAnotherThing();
} else {
    handleOtherCase();
}

-- cannot mix:
var x = if cond { longComputation() } else 0;   -- statement-form in expression: error
var x = if cond then longComputation() else 0;   -- expression-form: valid