Question
I am using Rust and learning it from a JavaScript background. I want to know whether one struct can extend another struct so that the new struct automatically contains all the fields of the original struct.
For example, in JavaScript with ES6 classes, I could write:
class Person {
constructor(gender, age) {
this.gender = gender;
this.age = age;
}
}
class Child extends Person {
constructor(name, gender, age) {
super(gender, age);
this.name = name;
}
}
I would like to do something conceptually similar in Rust.
A constraint is that the original struct comes from an external Cargo package, so I cannot modify its definition.
I found older material about single inheritance and virtual structs, but that feature was removed. I also found suggestions about using traits, but since the original type is from another crate, I do not think I can convert it into a trait.
What is the correct Rust approach for this situation?
Short Answer
By the end of this page, you will understand why Rust structs do not support field inheritance like JavaScript or classical OOP languages, and what to use instead. You will learn the idiomatic Rust solution: composition—putting one struct inside another—plus how traits and wrapper types help you model shared behavior safely and clearly.
Concept
Rust does not support struct inheritance where one struct automatically gains all the fields of another struct.
If you have a type from another crate and want your own type to include it, the standard Rust solution is composition:
struct Child {
person: Person,
name: String,
}
This means Child contains a Person instead of being a specialized Person through inheritance.
Why Rust avoids field inheritance
Rust is designed around:
- clear ownership
- explicit data layout
- predictable behavior
- composition over inheritance
Inheritance can blur the structure of data and create tight coupling. Rust prefers simpler building blocks:
- structs for storing data
- impl blocks for methods
- traits for shared behavior
- composition for combining types
Important point about external types
Even if Person comes from another crate, you can still use it as a field inside your own struct. You do not need to modify Person to do that.
What you lose compared to inheritance
With inheritance, you might write child.age directly if age came from Person.
With composition, you usually write:
child.person.age
That extra level is intentional: Rust makes structure explicit.
What you can still do
You can make your wrapper more convenient by adding methods:
impl Child {
fn age(&self) -> u32 {
self.person.age
}
}
If you need shared behavior rather than shared fields, traits are often the right abstraction.
Mental Model
Think of Rust structs like real-world objects packed in boxes.
- In inheritance-based languages, a
Childbox might be treated as a largerPersonbox with extra compartments added on. - In Rust, a
Childbox usually contains a separatePersonbox inside it.
So instead of saying:
- “Child is a Person with more fields”
Rust often says:
- “Child has a Person and also has more fields”
This is called composition.
A simple analogy:
- Inheritance: building a taller version of the same cabinet
- Composition: placing one cabinet inside a larger storage unit with extra drawers
Rust strongly prefers the second approach because it is explicit and easier to reason about.
Syntax and Examples
The core Rust pattern is to store the existing struct as a field.
struct Person {
gender: String,
age: u32,
}
struct Child {
person: Person,
name: String,
}
You create values like this:
fn main() {
let person = Person {
gender: String::from("female"),
age: 8,
};
let child = Child {
person,
name: String::from("Mia"),
};
println!("{} is {} years old", child.name, child.person.age);
}
If the original struct comes from another crate
That is still fine. You can do this:
use external_crate::Person;
struct Child {
person: Person,
name: String,
}
Adding convenience methods
If you want Child to expose part of Person, add methods:
struct Person {
gender: String,
age: u32,
}
struct Child {
person: Person,
name: String,
}
impl Child {
fn age(&self) -> u32 {
self.person.age
}
fn gender(&self) -> &str {
&self.person.gender
}
}
Now usage is simpler:
fn main() {
let child = Child {
person: Person {
gender: String::from("male"),
age: 10,
},
name: String::from("Leo"),
};
println!("{} is a {} year old {}", child.name, child.age(), child.gender());
}
Using a trait for shared behavior
If multiple types should behave like a person, use a trait:
trait HasAge {
fn age(&self) -> u32;
}
impl HasAge for Person {
fn age(&self) -> u32 {
self.age
}
}
impl HasAge for Child {
fn age(&self) -> u32 {
self.person.age
}
}
This does not copy fields, but it lets different types share an interface.
Step by Step Execution
Consider this example:
struct Person {
gender: String,
age: u32,
}
struct Child {
person: Person,
name: String,
}
fn main() {
let p = Person {
gender: String::from("female"),
age: 7,
};
let c = Child {
person: p,
name: String::from("Ava"),
};
println!("{} is {} years old", c.name, c.person.age);
}
Step by step
-
Personis defined with two fields:genderage
-
Childis defined with:person, which stores a fullPersonname, which is specific toChild
-
In
main, aPersonvalue is created and stored inp. -
Then a
Childvalue is created.- The field
person: pmoves thePersonintoChild. - The field
namestores a separate string.
- The field
-
When printing,
c.nameaccesses the child's own field. -
c.person.ageaccesses the nestedPersonfield insideChild.
Key observation
Rust does not flatten the fields of Person into Child.
So this is not valid:
// not valid
// println!("{}", c.age);
You must use the path through the contained struct:
println!("{}", c.person.age);
Real World Use Cases
Composition is used constantly in real Rust programs.
API request context
You may wrap a library type with your app-specific metadata:
struct AuthenticatedRequest {
request: HttpRequest,
user_id: String,
}
Database models
You might store a shared record and add view-specific fields:
struct UserProfile {
user: User,
is_online: bool,
}
Configuration objects
A crate may provide a base config type, and your app adds environment-specific settings:
struct AppConfig {
base: ExternalConfig,
debug_mode: bool,
}
Parsing and validation
A parsed object from a library can be wrapped with validation results:
struct CheckedDocument {
document: ExternalDocument,
is_valid: bool,
}
UI or game state
A reusable type can be embedded inside a richer application struct:
struct PlayerState {
person: Person,
score: u32,
}
In all these cases, composition keeps external types usable while letting your code add its own data.
Real Codebase Usage
In real Rust projects, developers usually combine composition with a few common patterns.
1. Wrapper structs
A type from another crate is placed inside your own type.
struct Child {
person: Person,
name: String,
}
This is the most common solution.
2. Convenience methods
To avoid repeated nested access, developers add methods:
impl Child {
fn age(&self) -> u32 {
self.person.age
}
}
This creates a cleaner public API.
3. Validation at construction time
A constructor can check inputs before creating the struct:
impl Child {
fn new(person: Person, name: String) -> Self {
Self { person, name }
}
}
If validation is needed:
impl Child {
fn try_new(person: Person, name: String) -> Result<Self, String> {
if name.trim().is_empty() {
return Err(String::from("name cannot be empty"));
}
Ok(Self { person, name })
}
}
4. Traits for shared behavior
If several types should expose the same operations, use a trait instead of inheritance.
trait Named {
fn name(&self) -> &str;
}
5. Delegation-style methods
If a contained type has useful methods, your outer type can forward to them.
impl Child {
fn gender(&self) -> &str {
&self.person.gender
}
}
This is sometimes called manual delegation.
6. Newtype pattern
If you only want to wrap one existing type, a tuple struct may be enough:
struct WrappedPerson(Person);
This is useful when adding methods, validation, or trait implementations without changing the original type.
Common Mistakes
Mistake 1: Expecting field inheritance
Beginners often try to access inner fields directly as if they were inherited.
struct Child {
person: Person,
name: String,
}
// wrong idea
// let age = child.age;
Use:
let age = child.person.age;
Or add a method:
impl Child {
fn age(&self) -> u32 {
self.person.age
}
}
Mistake 2: Thinking traits copy fields
Traits define behavior, not stored data.
trait HasAge {
fn age(&self) -> u32;
}
A trait cannot automatically add an age field to a struct.
Mistake 3: Assuming you must modify the external crate
You do not need to change the original struct to compose with it.
use external_crate::Person;
struct Child {
person: Person,
name: String,
}
This is valid and idiomatic.
Mistake 4: Confusing composition with duplication
You usually should not copy every field manually into a new struct unless there is a good reason.
Less ideal:
struct Child {
gender: String,
age: u32,
name: String,
}
This duplicates the structure and can drift from the original type.
Mistake 5: Forgetting ownership rules
When you move a Person into Child, you generally cannot use the original binding afterward unless the type implements Copy.
let p = Person {
gender: String::from("male"),
age: 9,
};
let c = Child {
person: p,
name: String::from("Noah"),
};
// p is moved here and usually cannot be used again
Mistake 6: Overusing inheritance thinking from other languages
In Rust, ask these questions instead:
- Do I need to store another type? Use composition.
- Do I need to share behavior? Use traits.
- Do I need to wrap a type with extra meaning? Use a wrapper or newtype.
Comparisons
| Concept | What it does | Includes fields automatically? | Best use |
|---|---|---|---|
| Inheritance | One type extends another | Yes in OOP languages | Classical OOP hierarchies |
| Composition | One struct contains another | No | Idiomatic Rust data modeling |
| Trait | Shares behavior through methods | No | Common interfaces across types |
| Newtype | Wraps a single existing type | No | Add meaning, methods, or trait impls |
Composition vs inheritance
| Question | Inheritance-style answer | Rust-style answer |
|---|---|---|
| How do I reuse fields? | Extend the base class | Store the struct as a field |
| How do I share methods? | Inherit methods | Implement methods or traits |
| How do I model “has a”? | Often awkward | Natural fit |
| How do I model “acts like”? | Base class interface | Trait |
Composition vs manual duplication
| Approach | Pros | Cons |
|---|---|---|
| Composition | Reuses the original type safely | Access is nested |
| Duplicate fields manually | Flat access | Repetition, drift, maintenance cost |
Trait vs struct field
| Need | Use trait? | Use contained struct? |
|---|---|---|
| Shared behavior only | Yes | Maybe not needed |
| Shared stored data | No | Yes |
| External type reuse | Sometimes | Yes |
| Add app-specific data | No | Yes |
Cheat Sheet
// Composition: preferred Rust approach
struct Child {
person: Person,
name: String,
}
Rules to remember
- Rust structs do not support field inheritance.
- Use composition to include one struct inside another.
- Use traits to share behavior, not fields.
- External crate types can still be used as fields in your own structs.
- Nested access looks like
child.person.age. - Add methods if you want a simpler API.
Common patterns
impl Child {
fn new(person: Person, name: String) -> Self {
Self { person, name }
}
fn age(&self) -> u32 {
self.person.age
}
}
If you only need behavior
trait HasAge {
fn age(&self) -> u32;
}
If you only need a wrapper
struct WrappedPerson(Person);
Quick decision guide
- Need another type's data inside yours? -> composition
- Need a shared method interface? -> trait
- Need to attach meaning to one existing type? -> newtype
- Need inherited fields like OOP classes? -> not available in Rust
FAQ
Can one struct inherit fields from another in Rust?
No. Rust does not support struct field inheritance. Use composition instead.
What is the Rust equivalent of extends for structs?
Usually there is no direct equivalent. The idiomatic replacement is to store one struct inside another.
Can I extend a struct from an external crate?
You cannot add fields to the original struct, but you can wrap it inside your own struct and add your own fields there.
Should I use traits instead of inheritance in Rust?
Use traits when you want shared behavior. Use composition when you want to store shared data.
Why does Rust prefer composition over inheritance?
Composition is explicit, flexible, and works well with Rust's ownership and safety model.
Can I make nested fields easier to access?
Yes. Add methods on your outer struct that return or expose the inner data.
Is duplicating all fields from the original struct a good idea?
Usually no. It creates maintenance problems and can drift from the original type definition.
What if I want my type to behave like the inner type?
Wrap the inner type and implement methods or traits that delegate to it.
Mini Project
Description
Build a small Rust program that models a student profile by wrapping an existing Person struct inside a Student struct. This demonstrates the Rust way to reuse data from one type while adding your own fields and methods.
Goal
Create a Student type that contains a Person, adds school-specific data, and exposes convenient methods without using inheritance.
Requirements
Create a Person struct with age and gender fields.
Create a Student struct that contains a Person and adds a name and grade field.
Implement a constructor method for Student.
Add methods that return the student's age and name.
Print a formatted summary of a student in main.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.