A struct alias would be a way to construct a new type from a previous one. Unlike type aliases, a struct alias is an owned type, so foreign traits can be implemented on it.
struct NewStruct = ForeignStruct;
Rationale
Their main purpose is to make the new type pattern more ergonomic and involve less boilerplate. The new type pattern serves as a good solution to sidestepping orphan rules, but it is usually avoided for the amount of boilerplate that it involves.
This is not meant to cover every single possible use case that there is to the new type pattern, because that may not be at all possible. But rather cover the most frequent and often most painful use cases.
Inheritance and shadowing
The new type will inherit the entire public API of the old type, which includes fields, methods, associated constants, and trait implementations. Additionally, the new type has the possibility of shadowing methods and associated constants when new ones are defined.
Shadow vs override
To be clear, struct aliases cannot override methods or trait implementations in a way that can influence the original type. They can only expose a different API shadowing the inherited one. This conforms with the traditional new type pattern where the new type can expose a different API to the old one, but cannot tinker with the old's type traits and method implementations.
For example:
struct Parent { pub field: bool };
impl Parent {
pub const NUM: i32 = 10;
pub fn get_num() -> i32 {
Self::NUM
}
}
struct Child = Parent;
impl Child {
// shadows Parent::NUM
// Parent::NUM still exists, but it cannot be accessed
// through Child::NUM. It can still be accessed through `get_num()`
pub const NUM: i32 = 99;
}
let _ = Child { field: bool }; // you can construct a Child just like a Parent.
assert_eq!(Child::NUM, 99); // Child::NUM shadows Parent::NUM
assert_eq!(Child::get_num(), 10); // note that this still returns Parent::NUM
Inherited traits can also be shadowed by implementing them over the type.
#[derive(Debug)]
struct Parent;
impl Parent {
fn print() {
println!("{Parent}");
}
}
struct Child = Parent;
impl Debug for Child {
fn fmt(&self, f: Formatter<'_>) -> fmt::Result<()> {
write!(f, "Child");
}
}
Child.print() // still prints "Parent"
println!("{}", Child); // prints "Child"
Casts
All struct aliases are types in their own right, so they do not support implicit coercion.
In order to cast one to another users must manually implement From
or use a derive macro.
Struct aliases are guaranteed to have the same layout,
so it is always safe to transmute one type into the other. They work as if the New type was defined with repr(transparent)
. This guarantee is important, because if the foreign type has private fields,
users will not be able to cast one type into the other.
// a From derive macro would expand to this.
let new = unsafe { transmute::<ForeignType, New>(foreign) };
Privacy
The privacy for all fields of a struct alias is set to be at most pub(crate)
.
This ensures that refactoring a struct alias into a proper struct is never a
breaking change.
// its ok to change this:
pub struct New = Old;
// into this, because library users weren't able to access the fields of `New`
pub struct New(Old);