Traits
Define shared behavior and provide statically dispatched implementations for different structs.
Core concepts
trait, impl, self, method calls, static dispatch
How the learner should approach this project
Do not paste the complete program immediately. Create the file, type the first step, run vpp check, and then run the program when a complete entry point exists. Read the explanation before looking at the snippet. After each step, predict what the new code should do. This turns the page into a lesson rather than a code dump.
Step 1: Define Circle
Circle is a concrete type that will later implement Area. Keeping the data definition separate from the behavior makes the role of the trait easier to see.
After typing the snippet, identify the new names introduced by the step. Ask what each name represents, what type the compiler should infer or check, and what value should exist after the code runs.
struct Circle {
radius: int
}Step 2: Define Square
Square is another concrete type that can provide its own implementation of the same Area behavior.
Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.
struct Square {
side: int
}Step 3: Declare the Area trait
The trait declares a required method. It describes behavior rather than storage. Implementations are selected statically by the compiler.
Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.
trait Area {
fn area(self) -> int
}Step 4: Begin the Circle implementation
The impl block connects Area to Circle. The method signature must satisfy the trait requirement.
Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.
impl Area for Circle {
fn area(self) -> int {
return self.radius * self.radius * 3
}
}Step 5: Implement Area for Square
Square uses the same method name but a different calculation. Static dispatch means the compiler knows which implementation belongs to the concrete type.
Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.
impl Area for Square {
fn area(self) -> int {
return self.side * self.side
}
}Step 6: Call methods from main
Method syntax makes the trait behavior easy to use. c.area selects the Circle implementation and s.area selects the Square implementation.
Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.
fn main() -> int {
let c = Circle { radius: 5 }
let s = Square { side: 4 }
print(c.area())
print(s.area())
return 0
}Complete source
struct Circle {
radius: int
}
struct Square {
side: int
}
trait Area {
fn area(self) -> int
}
impl Area for Circle {
fn area(self) -> int {
return self.radius * self.radius * 3
}
}
impl Area for Square {
fn area(self) -> int {
return self.side * self.side
}
}
fn main() -> int {
let c = Circle { radius: 5 }
let s = Square { side: 4 }
print(c.area())
print(s.area())
return 0
}Expected behavior
The complete program should produce the following output when run with the command shown below. Exact formatting should follow the current V++ runtime.
75 16
Run the project
vpp run projects/13-traits/main.vppWhat the learner should understand after this project
Traits describe shared behavior while impl blocks provide concrete behavior. Static dispatch lets the compiler resolve the method for a known concrete type.
Common mistakes to teach
- Trait method signature not matching the trait
- Implementing the trait for the wrong type
- Calling a method that the type does not implement
Practice extension
Create a Rectangle struct and implement Area for it. Explain why the method name can remain the same.
A strong learner should be able to explain the program without looking at the code, rebuild the core idea from memory, and make the practice change without copying a solution.
Full program
struct Circle {
radius: int
}
struct Square {
side: int
}
trait Area {
fn area(self) -> int
}
impl Area for Circle {
fn area(self) -> int {
return self.radius * self.radius * 3
}
}
impl Area for Square {
fn area(self) -> int {
return self.side * self.side
}
}
fn main() -> int {
let c = Circle { radius: 5 }
let s = Square { side: 4 }
print(c.area())
print(s.area())
return 0
}