Nitpick Object Orientation & Traits

Nitpick employs a strict, composition-over-inheritance model. It completely rejects classic class-based inheritance in favor of interfaces (Traits) and isolated data (Structs).

1. Traits and Implementation

A trait defines a set of functions that a type must implement to satisfy a behavioral contract.

trait:Serializable = {
    func:to_bytes = buffer(Self:self);
};

struct:Message = {
    int32:id;
};

impl:Serializable:for:Message = {
    func:to_bytes = buffer(Message:self) {
        // ... serialize logic ...
        pass result;
    };
};

2. Default Methods (v0.83.1)

Traits can provide default implementations. Impls may override or keep the default.

trait:Describable = {
    func:name = string(Self:self);              // required — must be implemented
    func:describe = string(Self:self) {         // default — optional to override
        pass("an object");
    };
};

3. Supertraits (v0.83.1)

A trait can require that implementing types also implement another trait:

trait:Ordered = Equatable & {
    func:compare = int32(Self:a, Self:b);
};

Impls of Ordered must also implement Equatable. The compiler enforces this transitively.

4. Associated Types (v0.83.2)

Traits can declare associated types that impls must bind to concrete types:

trait:Iterator = {
    Type:Item;
    func:next = Item(Self:self);
};

impl:Iterator:for:Range = {
    Type:Item = int32;
    func:next = int32(Range:self) { pass(self.current); };
};

Associated types can have defaults: Type:Error = string;

5. Inherent Impls (v0.83.3)

Methods can be attached directly to a type without any trait:

impl:for:Point = {
    func:magnitude = flt64(Point:self) {
        pass(flt64_sqrt(flt64(self.x * self.x + self.y * self.y)));
    };
};

6. Coherence & Object Safety (v0.83.3)

Coherence: At most one impl of a given trait for a given type. Overlapping impls produce a compile error.

Object safety: A trait is object-safe (usable as dyn Trait) if: 1. All methods take a self parameter (no static methods) 2. No method returns Self (unknown size at dyn time) 3. No methods have comptime type parameters

7. Derive Macros (v0.83.4)

The @derive attribute auto-generates trait implementations:

@derive(Default, PartialOrd, ToString, Eq, Hash, Clone, Debug, Ord)
struct:Config = {
    int32:priority;
    string:name;
};

8. Blanket Impls (v0.83.5)

A blanket impl auto-implements a trait for all types satisfying a bound:

impl:Loggable:for:T:where:Printable = {
    func:log_str = string(T:self) {
        pass("[LOG]");
    };
};

Concrete impls take priority over blanket-generated impls.

9. Dynamic Dispatch

By default, trait implementations are statically resolved at compile-time (monomorphization) to guarantee performance and zero-cost abstraction. If runtime polymorphism is absolutely required, developers must explicitly opt-in using the dyn keyword to create a “fat pointer” trait object.

Message:msg = Message{id: 1};
dyn Serializable:obj = msg;

Multi-bound dyn (v0.83.5)

A dyn type can require multiple traits:

dyn Drawable + Serializable:obj = msg;

dyn A + B is assignable to dyn A (widening). Each trait must be object-safe.

(Note: Because dynamic dispatch obfuscates the control flow graph, it triggers warnings under strict nitpick-safety profiles).

10. Data Hiding

All struct fields are pub (public) or private by default based on the module visibility rules. To strictly hide internal representations (e.g. for handles or FFI pointers), use the opaque keyword to define a type whose internal layout is entirely unknown to the consumer.

opaque:DatabaseHandle;