summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-20 10:49:40 +0000
committerZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-20 10:49:40 +0000
commitf027a812707d68ca0690b7544175b9f302dd57ad (patch)
tree8ca82006fa3213215ff7b70bec5e49e8421413ff
parent856198b2ea473a4fd2cd020e58db821265b21ca5 (diff)
Copy trait...
-rw-r--r--README.md36
-rw-r--r--src/parser/parser.h2
-rw-r--r--src/parser/parser_expr.c18
-rw-r--r--src/parser/parser_stmt.c2
-rw-r--r--std/mem.zc6
-rw-r--r--tests/features/test_copy_trait.zc41
6 files changed, 96 insertions, 9 deletions
diff --git a/README.md b/README.md
index 6c9ab96..c0275c7 100644
--- a/README.md
+++ b/README.md
@@ -189,6 +189,8 @@ struct Flags {
}
```
+> **Note**: Structs use [Move Semantics](#move-semantics--copy-safety) by default. Fields can be accessed via `.` even on pointers (Auto-Dereference).
+
#### Enums
Tagged unions (Sum types) capable of holding data.
```zc
@@ -347,6 +349,9 @@ These operators are built-in language features and cannot be overloaded directly
| `?.` | Safe Navigation | `ptr?.field` accesses field only if `ptr` is not NULL |
| `?` | Try Operator | `res?` returns error if present (Result/Option types) |
+**Auto-Dereference**:
+Pointer field access (`ptr.field`) and method calls (`ptr.method()`) automatically dereference the pointer, equivalent to `(*ptr).field`.
+
### 7. Printing and String Interpolation
Zen C provides versatile options for printing to the console, including keywords and concise shorthands.
@@ -409,6 +414,35 @@ Automatically free the variable when scope exits.
autofree var types = malloc(1024);
```
+#### Move Semantics & Copy Safety
+Zen C prevents double-free errors by enforcing **Move Semantics** for non-trivial types (structs) by default.
+
+- **Move by Default**: Assigning a struct variable transfers ownership. The original variable becomes invalid.
+- **Copy Trait**: Implement the `Copy` trait to opt-out of move semantics for simple types (e.g. `Point`).
+
+```zc
+struct Mover { val: int; }
+
+fn main() {
+ var a = Mover { val: 1 };
+ var b = a; // 'a' moved to 'b'
+ // print(a.val); // Error: Use of moved value 'a'
+}
+```
+
+**Opt-in Copy**:
+
+```zc
+struct Point { x: int; y: int; }
+impl Copy for Point {}
+
+fn main() {
+ var p1 = Point { x: 1, y: 2 };
+ var p2 = p1; // p1 copied to p2
+ // p1 is still valid
+}
+```
+
#### RAII / Drop Trait
Implement `Drop` to run cleanup logic automatically.
```zc
@@ -513,6 +547,8 @@ impl Drop for Resource {
}
```
+> **Note:** If a variable is moved, `drop` is NOT called on the original variable. It adheres to [Move Semantics](#move-semantics--copy-safety).
+
#### Composition
Use `use` to embed other structs. You can either mix them in (flatten fields) or name them (nest fields).
diff --git a/src/parser/parser.h b/src/parser/parser.h
index f82edf4..5757fbc 100644
--- a/src/parser/parser.h
+++ b/src/parser/parser.h
@@ -430,7 +430,7 @@ ASTNode *parse_impl_trait(ParserContext *ctx, Lexer *l);
ASTNode *parse_test(ParserContext *ctx, Lexer *l);
// Move semantics helpers
-int is_type_copy(Type *t);
+int is_type_copy(ParserContext *ctx, Type *t);
void check_move_usage(ParserContext *ctx, ASTNode *node, Token t);
ASTNode *parse_include(ParserContext *ctx, Lexer *l);
ASTNode *parse_import(ParserContext *ctx, Lexer *l);
diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c
index cc647c7..97d6218 100644
--- a/src/parser/parser_expr.c
+++ b/src/parser/parser_expr.c
@@ -9,7 +9,7 @@
Type *get_field_type(ParserContext *ctx, Type *struct_type, const char *field_name);
-int is_type_copy(Type *t)
+int is_type_copy(ParserContext *ctx, Type *t)
{
if (!t)
{
@@ -38,7 +38,11 @@ int is_type_copy(Type *t)
return 1;
case TYPE_STRUCT:
- // Structs are MOVE by default
+ // Structs are MOVE by default unless they implement Copy
+ if (check_impl(ctx, "Copy", t->name))
+ {
+ return 1;
+ }
return 0;
case TYPE_ARRAY:
@@ -1829,7 +1833,7 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l)
}
}
- if (!is_type_copy(t))
+ if (!is_type_copy(ctx, t))
{
Symbol *s = find_symbol_entry(ctx, arg->var_ref.name);
if (s)
@@ -1960,7 +1964,7 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l)
}
}
- if (!is_type_copy(t))
+ if (!is_type_copy(ctx, t))
{
Symbol *s = find_symbol_entry(ctx, arg->var_ref.name);
if (s)
@@ -2246,7 +2250,7 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l)
}
}
- if (!is_type_copy(t))
+ if (!is_type_copy(ctx, t))
{
Symbol *s = find_symbol_entry(ctx, arg->var_ref.name);
if (s)
@@ -3347,7 +3351,7 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec)
}
}
- if (!is_type_copy(t))
+ if (!is_type_copy(ctx, t))
{
Symbol *s = find_symbol_entry(ctx, arg->var_ref.name);
if (s)
@@ -3753,7 +3757,7 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec)
}
}
- if (!is_type_copy(t))
+ if (!is_type_copy(ctx, t))
{
Symbol *s = find_symbol_entry(ctx, rhs->var_ref.name);
if (s)
diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c
index 8fa0a1e..fc34a21 100644
--- a/src/parser/parser_stmt.c
+++ b/src/parser/parser_stmt.c
@@ -1248,7 +1248,7 @@ ASTNode *parse_var_decl(ParserContext *ctx, Lexer *l)
t = s->type_info;
}
}
- if (!is_type_copy(t))
+ if (!is_type_copy(ctx, t))
{
Symbol *s = find_symbol_entry(ctx, init->var_ref.name);
if (s)
diff --git a/std/mem.zc b/std/mem.zc
index 5f4de36..6627d72 100644
--- a/std/mem.zc
+++ b/std/mem.zc
@@ -15,6 +15,12 @@ trait Drop {
fn drop(self);
}
+trait Copy {}
+
+trait Clone {
+ // fn clone(self) -> Self;
+}
+
struct Box<T> {
ptr: T*;
}
diff --git a/tests/features/test_copy_trait.zc b/tests/features/test_copy_trait.zc
new file mode 100644
index 0000000..120dc5d
--- /dev/null
+++ b/tests/features/test_copy_trait.zc
@@ -0,0 +1,41 @@
+
+trait Copy {}
+
+struct Point {
+ x: int;
+ y: int;
+}
+
+impl Copy for Point {}
+
+struct Mover {
+ val: int;
+}
+
+fn test_copy_trait() {
+ var p1 = Point { x: 10, y: 20 };
+ var p2 = p1; // Copy, not move
+
+ // Both should be valid
+ assert(p1.x == 10, "p1 invalid after copy");
+ assert(p2.x == 10, "p2 invalid after copy");
+
+ // Modify p2, p1 should be unchanged
+ p2.x = 30;
+ assert(p1.x == 10, "p1 changed after p2 modification");
+ assert(p2.x == 30, "p2 modification failed");
+}
+
+fn test_move_default() {
+ var m1 = Mover { val: 1 };
+ var m2 = m1; // Moved
+
+ // Uncommenting this should cause compile error
+ // var m3 = m1;
+}
+
+fn main() {
+ test_copy_trait();
+ test_move_default();
+ "Copy Trait Works!";
+}