- Published on
Rust tricks
- Authors

- Name
- Garfield Zhu
- @_AlohaYo_
@Author: Garfield Zhu
Rust Tricks
Some interesting facts and knowledge points in learning Rust.
Struct and Trait
Think different from class concept in C++ or Java, data and method are separated in Rust.
1. No inheritance, but composition for struct.
For data in struct, Rust do not have inheritance, but recommend composition.
Given a struct B is composed by another struct A:
struct StructA {
x: i32;
};
struct StructB {
a: StructA,
// other fields...
};
We have a instance of B let b: B;, then wecan access A's member via: b.a.x;
Or, if we do not like the indirect way, but want a inherited style like a direct b.x, using the Deref and DerefMut traits will make it possible.
impl std::ops::Deref for StructB {
type Target = StructA;
fn deref(&self) -> &Self::Target {
&self.a
}
}
Then, we can make it real.
let b = StructB { a: StructA };
println!("{}", b.x);
2. Trait and dyn
Trait is a group of shared behavior, which looks like the concept interface.
Typically we use implement Trait for Struct to attach the methods to data. But since 1.0, Trait is used in double contexts.
As a trait: what is original designed, like:
impl MyTrait for StructA {}As a type: trait itself should also be allowed to be implemented, or implement other traits for it:
impl MyTrait {} impl AnotherTrait for MyTrait {}Implement a trait, is just like making a "interface default method" in Java. And implement trait for trait is used for using the methods in trait to implement another trait.
However, traits are actually unsized / dynamically-sized (or say "abstract"), using it as type leads to complex runtime errors.
To resolve this,
dynis introduced since 1.27. Read RFC 2113 for more details.It makes the real type of the trait object (used as type) to be decided at runtime via dynamic dispatch
trait Printable { fn stringify(&self) -> String; } impl Printable for i32 { fn stringify(&self) -> String { self.to_string() } } fn print(a: Box<dyn Printable>) { println!("{}", a.stringify()); } fn main() { print(Box::new(10) as Box<dyn Printable>); }
- See the answer for dyn on StackOverflow.
3. Trait Bounds and Supertrait
The above section mentioned
dynwhich decorates a trait to be a type. It's useful, but not always enough.Generics are much more expressive in type definition, and we can also need stipulate what functionality a generic type implements.
Bounds is what we can use trait to restrict what the generic needs to implement.
// Bounds Sample: fn printer<T: Display>(t: T) { println!("{}", t); }The bounds are common in other languages (typically uses keyword
extends), like the Bounded Type in Java, Generic Constraints, etc.Bounds allows muliple traits (with
+operator), andwhereclause to be more expressive.Rust doesn't have "inheritance", but you can define a trait as being a superset of another trait.
It's easy to understand since the supertraits just like the syntax of "extends" of "interface" (not class) in Java. It means: 1) implement the subtrait (child interface) must implement all methods in supertrait (parent interface); 2) one trait can have multiple supertrait.
trait Person { fn name(&self) -> String; } // Person is a supertrait of Student. // Implementing Student requires you to also impl Person. trait Student: Person { fn university(&self) -> String; } trait Programmer { fn fav_language(&self) -> String; } // CompSciStudent (computer science student) is a subtrait of both Programmer // and Student. Implementing CompSciStudent requires you to impl both supertraits. trait CompSciStudent: Programmer + Student { fn git_username(&self) -> String; } /** Showcase of using an object with multiple supertraits */ fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String { format!( "My name is {} and I attend {}. My favorite language is {}. My Git username is {}", student.name(), student.university(), student.fav_language(), student.git_username() ) }
4. Polymorphism
Polymorphism is real an important mechansim to make abstraction and reduce redundant code, no matter in OOP or FP. Though we have no inheritance for struct, but we can do polymorphism.
The above two sections mentioned:
a. dyn, which runs dynamic dispatch mechanism.
b. Geneirc bounds, which use trait to stipulate what functionality a type implements.
That's how polymorphism works in Rust. Use t: dyn Trait as the type of signature, the t.someMethod() will be dynamically decided using which implemention of Trait.
Example:
struct Circle {
radius: f64
}
struct Rectangle {
height: f64,
width: f64
}
trait Shape {
fn area(&self) -> f64;
}
impl Shape for Circle {
fn area(&self) -> f64 {
PI * self.radius * self.radius
}
}
impl Shape for Rectangle {
fn area(&self) -> f64 {
self.height * self.width
}
}
// Polymorphism with "dyn" trait type
fn print_area(shape: &dyn Shape) {
println!("{}", shape.area());
}
// Polymorphism by bounds on generic type
fn print_area_generic<T: Shape> (shape: &T) {
println!("{}", shape.area());
}
fn main() {
let circle = Circle{radius: 2.0};
let rectangle = Rectangle{height: 3.0, width: 5.0};
print_area(&circle); // 12.5664
print_area(&rectangle); // 15
print_area_generic(&circle); // 12.5664
print_area_generic(&rectangle); // 15
}
5. Delegate boxed/wrapped struct to trait object
- See this answer on StackOverflow.
It is widely used for convenience, we may have some structs encapulate the other types with trait implemented. And we want the implemented trait could be used on the wrapper struct.
The most common case is like Box<T>, Rc<T>, Arc<T>.
A widely used practise is implement trait for wrapper structs to delegate the implementation, like this:
impl<S: Solid + ?Sized> Solid for Box<S> {
fn intersect(&self, ray: f32) -> f32 {
(**self).intersect(ray)
// Some people prefer this less-ambiguous form
// S::intersect(self, ray)
}
}
Tips: bound ?Sized is necessary to make it could be optionally sized to support "S" as could be a trait type.
Formatter
Formatter trait makes it really simply to construct specific strings.
It is really useful for building strings or in a CLI tools.
Fill / Alignment
We need to align (to left or right) the strings with indents or some specific characters to an assigned length, or fill some characters to strings.
Using formatter with fill/alignment syntax is really helpful. It typically looks like {:0>8}
- Format syntax for fill/alignment:
assert_eq!("00000110", format!("{:0>8}", "110"));
// |||
// ||+-- width
// |+--- align
// +---- fill
The align bit supports the align mode to left, center and right.
< - the argument is left-aligned in width columns
^ - the argument is center-aligned in width columns
> - the argument is right-aligned in width columns
Example scenario:
We are implementing Display trait for a date time struct, like:
struct MyDateTime {
year: i32,
month: i32,
day: i32,
hour: i32,
minute: i32,
seconds: i32,
}
The data is in i32 structure. To display them in a standard format like "dd/MM/yyyy - hh:mm:ss", we need to fill some "0"s for the single-bit data. e.g. "01/02/2000 - 03:04:05".
So we can format the data:
- align to right;
- make length to be 2 for day/month/hour/minute/second, and 4 for year;
- fill the bits with "0"s to satify the length;
Now we have such a formatter for display:
impl fmt::Display for MyDateTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:0>2}/{:0>2}/{:0>4} - {:0>2}:{:0>2}:{:0>2}",
self.day,
self.month,
self.year,
self.hour,
self.minute,
self.second,
)
}
}
Reference
Rust Toolchain Cheat Sheet
If you already know Java, TypeScript/JavaScript, or Python, this table maps Rust concepts and tools to their nearest familiar counterparts. These are practical analogies rather than exact equivalents—Cargo, in particular, combines work that is often split across several tools in other ecosystems.
| Concept | Rust | Java | TypeScript / JavaScript | Python |
|---|---|---|---|---|
| Compiler / runtime | rustc (usually invoked through Cargo) | javac + JVM | TypeScript: tsc or a bundler; JavaScript: Node.js runtime | python interpreter (typically CPython) |
| Toolchain / version manager | rustup | SDKMAN! / jenv | nvm / fnm / Volta | uv python / pyenv |
| Package / project tool | Cargo | Maven / Gradle | npm / pnpm / Yarn | uv / pip / Poetry |
| Project manifest | Cargo.toml | pom.xml / build.gradle(.kts) | package.json | pyproject.toml |
| Lockfile | Cargo.lock | Maven: versions in the POM or a BOM; Gradle: dependency lockfiles | package-lock.json / pnpm-lock.yaml / yarn.lock | uv.lock / poetry.lock / Pipfile.lock; newer standard: pylock.toml |
| Package unit | Crate (Cargo package) | JAR / Maven artifact | npm package | Python distribution (.whl / sdist) |
| Internal code unit | Module (mod) | Java package | ES module | Python module / package |
| Multi-module project | Cargo workspace | Maven multi-module / Gradle multi-project | npm / pnpm / Yarn workspace | uv workspace |
| Public registry | crates.io | Maven Central | npm registry | PyPI |
| Add a dependency | cargo add clap | Add it to pom.xml / build.gradle(.kts) | pnpm add commander | uv add typer |
| Compile / type-check | cargo check | mvn compile | tsc --noEmit (TypeScript) | No required ahead-of-time compile step; python -m compileall checks syntax, while mypy / pyright check types |
| Build | cargo build | mvn package | tsc, vite build, tsup, etc. | uv build / python -m build (wheel + source distribution) |
| Run an app | cargo run | mvn spring-boot:run / java -jar | node, tsx, npm run ... | python app.py / python -m package / uv run ... |
| Unit tests | cargo test | JUnit + Maven / Gradle | Vitest / Jest | pytest / unittest |
| Integration tests | tests/ + cargo test | JUnit / Testcontainers | Vitest / Jest / Playwright | pytest (often with Testcontainers or other plugins) |
| Formatting | cargo fmt | Spotless / formatter plugins | Prettier | ruff format / Black |
| Linting | cargo clippy | Checkstyle / SpotBugs / Error Prone | ESLint | Ruff (ruff check) / mypy |
| Documentation | cargo doc | Javadoc | TypeDoc | pydoc / Sphinx / MkDocs |
| CLI argument parsing | clap | picocli | Commander / yargs | argparse / Click / Typer |
| Error handling | Result<T, E>, anyhow, thiserror | Exceptions | Exceptions / Result-style libraries | Exceptions |
| Distribution | Native executable | JAR + JVM / native image | JS bundle + Node.js, or a packaged executable | Wheel / source distribution; normally needs Python |
| Cross-platform build | Usually compile per OS / architecture | Usually one JAR, given a compatible JVM | Usually one JS bundle, given compatible runtime APIs | Pure-Python packages usually travel well; native extensions need platform-specific wheels |
| Binary size / runtime dependency | Native binary; no Rust runtime required | JVM normally required (unless using a native image) | Node.js normally required (unless packaged with the app) | Python interpreter normally required |