Field Embedding with `using`
using embeds one struct inside another and promotes the embedded
struct's fields into the outer struct's dot-access.
Think of it as "flat read-access without copy": p.x means p.base.x.
Named embed, anonymous embed, outer-wins shadowing, transitive chains, and the no-subtyping rule.
// Feature: using field embedding
// Syntax: `using base: Type` (named) or `using Type` (anonymous)
// Effect: promotes the embedded struct's fields into the outer struct's dot-access.
// FIELD-ONLY: methods of the embedded type are NOT forwarded.
// ---------- Struct definitions ----------
struct Entity {
x: int,
y: int,
}
struct Player {
using base: Entity,
hp: int,
}
struct Vec2 {
x: int,
y: int,
}
struct Particle {
using Vec2, // anonymous embed — implicit field name: vec2
mass: int,
}
struct Base {
name: str,
}
struct Derived {
using base: Base,
name: str, // outer field shadows the promoted one
}
struct A { v: int }
struct B { using a: A }
struct C { using b: B }
// `using` also works in the compact (positional) struct form, including the
// inline method block: `self.x` here promotes through the `base` embed.
struct Sprite(using base: Entity, tag: str) {
fn label(self) -> str { return "{self.tag}@{self.x},{self.y}" }
}
fn teleport(e: Entity) {
print("{e.x},{e.y}")
}
fn main() {
// ---------- named embed ----------
let p = Player { base: Entity { x: 10, y: 20 }, hp: 100 }
// Promoted field access — p.x desugars to p.base.x.
print(p.x) // 10
print(p.y) // 20
print(p.hp) // 100
// The embed field is also reachable directly.
print(p.base.x) // 10
// ---------- anonymous embed ----------
let q = Particle { vec2: Vec2 { x: 3, y: 4 }, mass: 5 }
print(q.x) // 3 — promoted from q.vec2.x
print(q.mass) // 5
// ---------- outer field wins ----------
let d = Derived { base: Base { name: "base" }, name: "outer" }
print(d.name) // outer (outer field wins)
print(d.base.name) // base
// ---------- transitive ----------
let c = C { b: B { a: A { v: 42 } } }
print(c.v) // 42 — promoted through B then C
// ---------- compact (positional) form ----------
let s = Sprite { base: Entity { x: 1, y: 2 }, tag: "hero" }
print(s.x) // 1 — promoted from s.base.x
print(s.label()) // hero@1,2 — `self.x` promoted inside compact method
// ---------- no subtyping ----------
// Player cannot be passed where Entity is expected.
// Pass p.base explicitly when Entity is required.
teleport(p.base) // 10,20
}
Named vs anonymous embed
// Named — you pick the field name.
struct Player {
using base: Entity,
hp: int,
}
// Anonymous — the field name is the type's last segment, lowercased.
struct Particle {
using Vec2, // implicit field name: vec2
mass: int,
}
Construction always uses the real field name:
let p = Player { base: Entity { x: 0, y: 0 }, hp: 100 }
let q = Particle { vec2: Vec2 { x: 1, y: 2 }, mass: 3 }
What is (and isn't) promoted
- Fields of the embedded type are promoted —
p.xworks. - Methods and trait impls of the embedded type are not forwarded.
Call
p.base.greet()explicitly. Playeris not a subtype ofEntity. Passp.basewhen a function expects anEntity.- Promotion only applies to statically typed receivers
(
let p: Player = ..., parameters,self, struct literals). A dynamically typed receiver is left as-is.
Priority and ambiguity
An outer field always shadows a same-named promoted field:
struct Derived {
using base: Base,
name: str, // wins over Base.name
}
If two using fields promote the same name with no outer field to resolve
the tie, the compiler raises TE112. Qualify the access explicitly:
d.left.value or d.right.value.
Transitive embedding
Promotion chains through multiple using levels:
struct A { v: int }
struct B { using a: A }
struct C { using b: B }
let c = C { b: B { a: A { v: 42 } } }
print(c.v) // 42
Errors
| Code | Meaning |
|---|---|
| TE112 | Two using fields promote the same name — qualify the access. |
| TE113 | The using field's type is not a struct (e.g. using base: int). |
LSP note
Hover and go-to-definition on a promoted field (e.g. p.x) currently
return no result — the LSP does not run the using desugar pass, so it
does not recognize promoted field accesses. Use the qualified form
p.base.x in the editor to get hover and navigation. Full promoted-field
IDE support is a known follow-up.