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.
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 42 Instantiate PersonBuilder^ As <builder> = PersonBuilder::Constructor(); 43 Instantiate Person^ As <person> = builder 44 .withName(String::Constructor("Alice")) 45 .withAge(Integer::Constructor(30)) 46 .withEmail(String::Constructor("alice@example.com")) 47 .build();
Tip
&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.
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 36 Instantiate Cache<String, String>^ As <cache> = Cache@String, String::Constructor(); 37 38 Run cache.put(String::Constructor("key1"), String::Constructor("value1")); 39 40 // Get returns a reference, doesn't copy 41 Instantiate String& As <value> = cache.get(String::Constructor("key1")); 42 Run Console::printLine(value);
| Operation | Ownership 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.
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 16 Instantiate String^ As <original> = String::Constructor(" hello world "); 17 Instantiate StringProcessor^ As <processor> = StringProcessor::Constructor(); 18 19 Instantiate String^ As <processed> = processor.process(original); 20 21 Run Console::printLine(original); // " hello world " 22 Run Console::printLine(processed); // "HELLO WORLD"
Note
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.
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 36 Instantiate ConnectionFactory^ As <factory> = ConnectionFactory::Constructor( 37 String::Constructor("localhost"), 38 Integer::Constructor(8080) 39 ); 40 41 Instantiate Connection^ As <conn1> = factory.create(); 42 Instantiate Connection^ As <conn2> = factory.createWithTimeout(Integer::Constructor(5000));
Tip
^ (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.
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 31 Instantiate EventEmitter^ As <emitter> = EventEmitter::Constructor(); 32 33 // Copy capture - prefix is copied into lambda 34 Instantiate String^ As <prefix> = String::Constructor("[LOG]"); 35 36 Instantiate 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 42 Run emitter.on(logger); 43 Run emitter.emit(String::Constructor("Application started"));
| Capture Mode | When 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.
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
Pattern 7: Option Type
Represent optional values safely with ownership.
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 50 Method <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 59 Instantiate Option<User>^ As <maybeUser> = findUser(Integer::Constructor(123)); 60 61 If (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.
| Scenario | Recommended | Why |
|---|---|---|
| Creating new values | Return T^ | Caller owns the new object |
| Accessing internal data | Return T& | No ownership transfer needed |
| Small values (int, bool) | Parameter T% | Copy is cheap |
| Read-only large data | Parameter T& | Avoid expensive copies |
| Taking ownership | Parameter T^ | Clear ownership transfer |
| Lambda captures (long-lived) | [%var] | Lambda needs its own copy |
| Lambda captures (short-lived) | [&var] | Reference is sufficient |
Warning
& (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.