strong
strong declares a strong owning ARC reference. A strong handle increments the allocation's strong count, keeps the value alive for at least as long as the handle itself lives, and participates in deterministic destruction when the count reaches zero. Use strong for dominant owners; pair with weak to break cycles.
Ground truth: Lexer
TokenKind::Strong(src/parsing/lexer.rs:1012, keyword"strong"), ASTStmtKind::StrongDeclaration(StrongDecl, bool /*export*/)(src/parsing/ast.rs:587),StrongDecl { name, expr, type_ann }(ast.rs:954-958),OwnershipKind::Strong(ast.rs:939), runtimeValue::Share(StrongRef)viamemory::arc::shared_object::{StrongRef,allocate_share,create_weak_from_strong}(ast.rs:1629), loweringStmtKind::StrongDeclarationinhir_lower.rs:57.
Syntax
strong_decl ::= "strong" ident (":" type)? "=" expr ";"
export_strong ::= "export"? "strong" ident ...
type ::= ident | generic_type | ...
strong is a declaration qualifier like share and weak, not a type operator. It appears at statement start.
Minimal grammar
strong owner = expr;
strong owner: T = expr;
export strong shared_cache = make_cache();
Lexer
Reserved as TokenKind::Strong. Not usable as identifier; case-sensitive strong only.
Parser → AST
// src/parsing/ast.rs:954
pub struct StrongDecl {
pub name: String,
pub expr: Expr,
pub type_ann: Option<String>,
}
Stored as StmtKind::StrongDeclaration(decl, export_flag) where export_flag captures export strong ....
HIR / MIR
Lowered in hir_lower.rs:57: initializer evaluated, then allocate_share (ast.rs:1628) allocates SharedObject { value, strong:1, weak:0 } and returns StrongRef. HirExpr::Share may alias the handle (hir.rs:230). Assignment of a strong value clones the StrongRef (atomic increment).
Value representation at runtime (ast.rs:1020):
Value::Share(StrongRef) // strong ref (used for both share and strong today)
Value::Weak(WeakRef) // created via weak decl / downgrade
Debug formatting shows counts (ast.rs:1168):
<strong ref count=2, weak_count=1, value=[1, 2, 3]>
Semantics
ARC strong ownership
strong a = [1,2,3] // SharedObject { strong=1, weak=0 }
strong b = a // clone StrongRef → strong=2
{
strong c = a // strong=3
} // c dropped → strong=2
// a,b dropped → strong=0 → SharedObject::drop frees value
// if any WeakRef existed (weak>0), control block lingers until last Weak dropped
- Strong count = number of
share+stronghandles aliased to the sameSharedObject. Value lives whilestrong > 0. - Weak count = number of
WeakRefobservers (weakdecls or.downgrade()results). Observers do not keep value alive and mustupgrade()to obtain a temporaryStrongRef. - Thread safety:
StrongRef/WeakRefatomics use the underlying ARC runtime; current interpreter counts are atomic. - Destruction: Last strong drop destroys
valueimmediately (deterministic, no GC pause) but may leave the control block until last weak dropped.
Compilation pipeline
- Lex
strong→TokenKind::Strong. - Parse
StrongDecl { name, expr, type_ann }. - Optimize
ast_optimizer.rs:404folds initializer. - Lower →
allocate_share(initial_value)then bind name toStrongRef. - Borrow / ownership check —
OwnershipKind::Strongtracked likeShared; exclusive borrows conflict with shared/strong at CFG join (merge.rs:174). - Drop insertion — each strong handle gets a drop point at scope exit /
RegionExitboundary. - Codegen — retains are
strong_ref.clone(), releases aredrop(strong_ref); FFI boundary keeps count consistent viainvoke_arc_method(ast.rs:1629).
OwnershipKind context
pub enum OwnershipKind { Unique, Shared, Strong, Weak }
Strong and Shared (via share) both map to owning ARC; Unique is the implicit let single-owner form; Weak is non-owning (WeakDecl). The checker uses OwnershipKind to suggest fixes (ownership_help::use_after_move, use_weak_for_cycle in error.rs:562).
Interaction with other primitives
share— same runtime handle type, distinct intent:share= cache/interned shared-mutable resource,strong= dominant owner. Intermix freely — counts combine.weak— borrow a strong/share without incrementingstrong. Prevents cycles likeparent: strong, child: weak back-link.region/alloc/free— strong memory is not arena-allocated nor manually freed. Do notfreeastronghandle; allocation/free inside a strong-valued object's field still needs manualfreebefore final drop.unsafe—stronghandles remain safe to share acrossunsafeboundaries, but derive raw pointers viastrong.get()pattern keeps borrow analysis honest.- Escape/
spawn— ARC types are considered safe for concurrent capture (escape_analysis.rs:631).
Examples
Example 1 — Strong ownership, cloning, and deterministic drop
strong root = Node { value: 1 };
print(root.value); // 1
strong alias = root; // strong=2
print(alias.value); // 1 — aliased, not copied
// Dropping one handle leaves value alive via the other:
{
strong inner = root; // strong=3
print("inner alive");
} // inner dropped → strong=2
print(root); // still alive, value=1
// After last strong dropped at scope exit → deallocated
Effectively:
strong a = [1, 2, 3];
print(a); // <strong ref count=1, weak_count=0, value=[1,2,3]> in debug
strong b = a; // bumps count
print(a); // <strong ref count=2, ...> — same allocation, pointer equality
print(b == a); // true (handle equality, not element-wise)
Example 2 — Cycle risk and breaking it with weak (graph/parent-child)
struct Node { value: i32, next: Weak<Node> | null, child: strong Node | null }
// Pitfall: strong cycle leaks!
strong parent = Node { value: 1, next: null, child: null };
strong child = Node { value: 2, next: null, child: null };
// If we write parent.next = child and child.next = parent as strong, both keep each other alive forever.
// Solution: back-edge is weak.
weak back = parent; // weak=1 (observer)
parent.child = child; // parent (strong=1) owns child
// child models back-link as weak — not automatically upgraded
fn parent_of(c) {
let p = back.upgrade(); // returns StrongRef or null
if p != null { print(p.value); } // 1
else { print("parent dropped"); }
}
parent_of(child); // 1
// When parent goes out of scope, back.upgrade() -> null, child can still be dropped.
Note: Weak<T>.upgrade() → StrongRef | null, and WeakRef debug format (ast.rs:1178) prints <weak ref count=1, weak_count=1>.
Example 3 — export strong and mixing qualifiers
// Shared configuration interned at module top-level
export strong CONFIG = Config { host: "localhost", port: 8080 };
// Consumers can alias without moving — moves would destroy single owner:
strong local_cfg = CONFIG; // alias, CONFIG still valid
print(local_cfg.host); // localhost
print(CONFIG.host); // localhost — not moved
// share vs strong interop — both increment the same counter
share cache = [1, 2, 3];
strong alias = cache; // strong=2
share another = alias; // strong=3 — counters are combined
weak observer = cache; // weak=1 — does not bump strong
print(alias); // <strong ref count=3, weak_count=1, value=[1,2,3]>
print(observer); // <weak ref count=3, weak_count=1>
// Cleanup order demonstration with defer-style reasoning
region scratch {
strong tmp = alloc_graph(); // ARC heap, outlives region if aliased outside
strong leaked_out = tmp; // strong escape
} // scratch bulk-free runs, but tmp's ARC value stays alive via leaked_out
print(leaked_out); // valid — region did not free ARC memory
Restrictions / Errors
| Code / Kind | Trigger | Diagnostics |
|---|---|---|
| Parse | let strong = 1 or x.strong as identifier | unexpected token 'strong', expected identifier — TokenKind::Strong reserved |
| Parse | strong x; missing = expr | expected '=' after strong declaration at StrongDecl span |
| Type | strong x: T = expr where expr not subtype of T | TypeError: expected T, found U with type_ann span |
Borrow BorrowKindConflict | Mixing strong/share with simultaneous exclusive borrow at CFG join | merge.rs:174 — narrow exclusive scope before aliasing |
| Ownership | Cycle with only strongs leaks | No compiler error (leak is semantic). Lint suggests error::ownership_help::use_weak_for_cycle: "keep one side as weak instead of strong/share only. Upgrade with .upgrade()" (error.rs:562) |
| Use after move | let moved = unique_value; strong alias = moved; use of moved | ownership_help::use_after_move suggests share/strong/weak instead of moving a let unique (error.rs:499) |
| Mismatch | free(strong_handle) | free expects *mut T, found Share/StrongRef — ARC memory not manually freed |
| Export | export strong x = ... outside module top-level | Parser restricts export flag; error notes export only at top-level |
Debugging tip: Print a strong handle with debug formatting to inspect strong_count / weak_count (ast.rs:1169). If strong_count never reaches 0, look for a missing scope exit or a strong cycle that should be weak.
See Also
- share —
ShareDecl/OwnershipKind::Shared, copyable shared alias - weak —
WeakDecl/WeakRef, non-owning observer andupgrade()pattern - region — arena bulk-free vs ARC scope-based drop (
DropReason::RegionExit) - alloc / free — manual memory vs ARC-managed memory
- Memory & Safety — ARC patterns, breaking cycles
- Lexer & Parser —
TokenKind::Strong/ShareDeclaration src/parsing/ast.rs:587,954-958,1020-1022StrongDeclaration,Value::Sharesrc/memory/arc/shared_object.rsStrongRef,WeakRef,allocate_share,StrongRef::strong_count/weak_count