Ownership Patterns

Learn common patterns for working with XXML's ownership system using ^ (owned), & (reference), and % (copy).

^

Owned

Full ownership, responsible for cleanup

&

Reference

Borrowed access, no ownership

%

Copy

Independent copy of the value

Pattern 1: Builder Pattern

The builder pattern works well with ownership. Each method returns a reference to allow chaining, while the final build method returns an owned value.

builder_pattern.xxml
1[ Class <PersonBuilder> Final Extends None
2 [ Private <>
3 Property <name> Types String^;
4 Property <age> Types Integer^;
5 Property <email> Types String^;
6 ]
7
8 [ Public <>
9 Constructor Parameters () ->
10 {
11 Set name = String::Constructor("");
12 Set age = Integer::Constructor(0);
13 Set email = String::Constructor("");
14 }
15
16 Method <withName> Returns PersonBuilder& Parameters (Parameter <n> Types String^) Do
17 {
18 Set name = n;
19 Return &self;
20 }
21
22 Method <withAge> Returns PersonBuilder& Parameters (Parameter <a> Types Integer%) Do
23 {
24 Set age = a;
25 Return &self;
26 }
27
28 Method <withEmail> Returns PersonBuilder& Parameters (Parameter <e> Types String^) Do
29 {
30 Set email = e;
31 Return &self;
32 }
33
34 Method <build> Returns Person^ Parameters () Do
35 {
36 Return Person::Constructor(name, age, email);
37 }
38 ]
39]
40
41// Usage
42Instantiate PersonBuilder^ As <builder> = PersonBuilder::Constructor();
43Instantiate Person^ As <person> = builder
44 .withName(String::Constructor("Alice"))
45 .withAge(Integer::Constructor(30))
46 .withEmail(String::Constructor("alice@example.com"))
47 .build();

Tip

Builder methods return &self to enable method chaining while keeping the builder alive. The final build() returns an owned ^ value.

Pattern 2: Container with Borrowed Access

Containers own their data but provide borrowed references for access. This prevents copying while maintaining safety.

container_pattern.xxml
1[ Class <Cache> <K Constrains None, V Constrains None> Final Extends None
2 [ Private <>
3 Property <data> Types Collections::HashMap<K, V>^;
4 ]
5
6 [ Public <>
7 Constructor Parameters () ->
8 {
9 Set data = Collections::HashMap@K, V::Constructor();
10 }
11
12 // Takes ownership of both key and value
13 Method <put> Returns None Parameters (
14 Parameter <key> Types K^,
15 Parameter <value> Types V^
16 ) Do
17 {
18 Run data.put(key, value);
19 }
20
21 // Returns borrowed reference - doesn't transfer ownership
22 Method <get> Returns V& Parameters (Parameter <key> Types K&) Do
23 {
24 Return data.get(key);
25 }
26
27 // Check without borrowing
28 Method <contains> Returns Bool^ Parameters (Parameter <key> Types K&) Do
29 {
30 Return data.contains(key);
31 }
32 ]
33]
34
35// Usage - cache owns all data
36Instantiate Cache<String, String>^ As <cache> = Cache@String, String::Constructor();
37
38Run cache.put(String::Constructor("key1"), String::Constructor("value1"));
39
40// Get returns a reference, doesn't copy
41Instantiate String& As <value> = cache.get(String::Constructor("key1"));
42Run Console::printLine(value);
OperationOwnership Behavior
put(K^, V^)Takes ownership of key and value
get(K&)Borrows key, returns borrowed value
contains(K&)Borrows key, returns new Bool

Pattern 3: Processing Pipeline with Copies

When you need to process data without modifying the original, use copy parameters.

pipeline_pattern.xxml
1[ Class <StringProcessor> Final Extends None
2 [ Public <>
3 Constructor = default;
4
5 // Takes a copy - doesn't affect original
6 Method <process> Returns String^ Parameters (Parameter <input> Types String%) Do
7 {
8 Instantiate String^ As <result> = input.toUpperCase();
9 Set result = result.trim();
10 Return result;
11 }
12 ]
13]
14
15// Original is unchanged
16Instantiate String^ As <original> = String::Constructor(" hello world ");
17Instantiate StringProcessor^ As <processor> = StringProcessor::Constructor();
18
19Instantiate String^ As <processed> = processor.process(original);
20
21Run Console::printLine(original); // " hello world "
22Run Console::printLine(processed); // "HELLO WORLD"

Note

Using String% creates a copy of the input, ensuring the original string remains unchanged after processing.

Pattern 4: Factory Pattern

Factory methods return owned values since they create new objects.

factory_pattern.xxml
1[ Class <ConnectionFactory> Final Extends None
2 [ Private <>
3 Property <host> Types String^;
4 Property <port> Types Integer^;
5 ]
6
7 [ Public <>
8 Constructor Parameters (
9 Parameter <h> Types String^,
10 Parameter <p> Types Integer%
11 ) ->
12 {
13 Set host = h;
14 Set port = p;
15 }
16
17 // Factory method returns owned connection
18 Method <create> Returns Connection^ Parameters () Do
19 {
20 Return Connection::Constructor(host, port);
21 }
22
23 // Create with custom timeout
24 Method <createWithTimeout> Returns Connection^ Parameters (
25 Parameter <timeout> Types Integer%
26 ) Do
27 {
28 Instantiate Connection^ As <conn> = Connection::Constructor(host, port);
29 Run conn.setTimeout(timeout);
30 Return conn;
31 }
32 ]
33]
34
35// Each call creates a new owned connection
36Instantiate ConnectionFactory^ As <factory> = ConnectionFactory::Constructor(
37 String::Constructor("localhost"),
38 Integer::Constructor(8080)
39);
40
41Instantiate Connection^ As <conn1> = factory.create();
42Instantiate Connection^ As <conn2> = factory.createWithTimeout(Integer::Constructor(5000));

Tip

Factory methods always return ^ (owned) because they create new objects that the caller should own and manage.

Pattern 5: Callback with Reference

When passing lambdas that capture data, choose the right capture mode.

callback_pattern.xxml
1[ Class <EventEmitter> Final Extends None
2 [ Private <>
3 Property <handlers> Types Collections::List<F(None)(String&)>^;
4 ]
5
6 [ Public <>
7 Constructor Parameters () ->
8 {
9 Set handlers = Collections::List@F(None)(String&)::Constructor();
10 }
11
12 Method <on> Returns None Parameters (
13 Parameter <handler> Types F(None)(String&)^
14 ) Do
15 {
16 Run handlers.add(handler);
17 }
18
19 Method <emit> Returns None Parameters (Parameter <event> Types String&) Do
20 {
21 For (Integer^ <i> = 0 .. handlers.size()) ->
22 {
23 Instantiate F(None)(String&)& As <handler> = handlers.get(i);
24 Run handler.call(event);
25 }
26 }
27 ]
28]
29
30// Usage with different capture modes
31Instantiate EventEmitter^ As <emitter> = EventEmitter::Constructor();
32
33// Copy capture - prefix is copied into lambda
34Instantiate String^ As <prefix> = String::Constructor("[LOG]");
35
36Instantiate F(None)(String&)^ As <logger> = [ Lambda [%prefix] Returns None Parameters (
37 Parameter <msg> Types String&
38) {
39 Run Console::printLine(prefix.concat(String::Constructor(" ")).concat(msg));
40}];
41
42Run emitter.on(logger);
43Run emitter.emit(String::Constructor("Application started"));
Capture ModeWhen to Use
[%var]Lambda outlives the original variable
[&var]Short-lived lambda, original stays in scope
[^var]Transfer ownership to the lambda

Pattern 6: RAII - Resource Acquisition

Ownership ensures resources are released when they go out of scope.

raii_pattern.xxml
1[ Class <FileHandle> Final Extends None
2 [ Private <>
3 Property <file> Types IO::File^;
4 Property <path> Types String^;
5 ]
6
7 [ Public <>
8 Constructor Parameters (Parameter <filePath> Types String^) ->
9 {
10 Set path = filePath;
11 Set file = IO::File::Constructor(filePath, String::Constructor("r"));
12 }
13
14 // File is automatically closed when FileHandle is destroyed
15
16 Method <readAll> Returns String^ Parameters () Do
17 {
18 Return IO::File::readAll(path);
19 }
20 ]
21]
22
23// Usage - file is automatically closed when handle goes out of scope
24{
25 Instantiate FileHandle^ As <handle> = FileHandle::Constructor(
26 String::Constructor("data.txt")
27 );
28 Instantiate String^ As <content> = handle.readAll();
29 Run Console::printLine(content);
30} // handle goes out of scope, file is closed

Note

RAII (Resource Acquisition Is Initialization) ensures resources like files, connections, and locks are automatically cleaned up when their owner goes out of scope.

Pattern 7: Option Type

Represent optional values safely with ownership.

option_pattern.xxml
1[ Class <Option> <T Constrains None> Final Extends None
2 [ Private <>
3 Property <value> Types T^;
4 Property <hasValue> Types Bool^;
5 ]
6
7 [ Public <>
8 // Create empty option
9 Constructor Parameters () ->
10 {
11 Set hasValue = Bool::Constructor(false);
12 }
13
14 // Create option with value
15 Constructor Parameters (Parameter <v> Types T^) ->
16 {
17 Set value = v;
18 Set hasValue = Bool::Constructor(true);
19 }
20
21 Method <isSome> Returns Bool^ Parameters () Do
22 {
23 Return hasValue;
24 }
25
26 Method <isNone> Returns Bool^ Parameters () Do
27 {
28 Return hasValue.not();
29 }
30
31 // Returns reference - caller doesn't take ownership
32 Method <unwrap> Returns T& Parameters () Do
33 {
34 Return &value;
35 }
36
37 // Returns owned value with default
38 Method <unwrapOr> Returns T^ Parameters (Parameter <default> Types T^) Do
39 {
40 If (hasValue) -> {
41 Return value;
42 } Else -> {
43 Return default;
44 }
45 }
46 ]
47]
48
49// Usage
50Method <findUser> Returns Option<User>^ Parameters (Parameter <id> Types Integer%) Do
51{
52 If (userExists(id)) -> {
53 Return Option@User::Constructor(loadUser(id));
54 } Else -> {
55 Return Option@User::Constructor();
56 }
57}
58
59Instantiate Option<User>^ As <maybeUser> = findUser(Integer::Constructor(123));
60
61If (maybeUser.isSome()) -> {
62 Instantiate User& As <user> = maybeUser.unwrap();
63 Run Console::printLine(user.name);
64}

Best Practices Summary

When to Return ^ (Owned)

Factory methods, constructors, and any method creating new values. The caller becomes responsible for the object's lifetime.

When to Return & (Reference)

Getters, accessors, and methods providing access to internal data. The original owner retains control and cleanup responsibility.

When to Accept % (Copy)

Small primitive values, when you need independence from the original, or when the function should not affect the caller's data.

ScenarioRecommendedWhy
Creating new valuesReturn T^Caller owns the new object
Accessing internal dataReturn T&No ownership transfer needed
Small values (int, bool)Parameter T%Copy is cheap
Read-only large dataParameter T&Avoid expensive copies
Taking ownershipParameter T^Clear ownership transfer
Lambda captures (long-lived)[%var]Lambda needs its own copy
Lambda captures (short-lived)[&var]Reference is sufficient

Warning

When in doubt, prefer & (reference) for parameters to avoid unnecessary copies or ownership transfers. Only use ^ when you truly need to take ownership.

Next Steps

Now that you understand ownership patterns, explore the ownership reference for detailed semantics, or check out the Collections module to see ownership in action with data structures.