This post is about functions in Rust accepting struct instances that implement a specific trait. We will start off with simple Java codes that implements Java interfaces. Next, we will convert the Java codes to Rust keeping the same functionality.
Function, Struct, and Trait From Java Perspective
If this was for Java, the title would be “Method Accepts Class That Implements An Interface.” Still, we should not forget that Java is different from Rust.
Java Interfaces
We would have the following interfaces to implement. Any class that implements the Bounceable and Inflatable interfaces has the methods bounce and inflate.
1 2 3 4 5 6 7 8 9 | interface Bounceable { void bounce(); } interface Inflatable { void inflate(); } |
Java Interface Implementations
We have two classes – BasketBall and TennisBall. Both balls bounce, but only one of them is inflatable. Consider the following codes. The BasketBall class implements both the Bounceable and Inflatable interfaces, but the TennisBall only implements Bounceable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // A basketball is both bounceable and inflatable class BasketBall implements Bounceable, Inflatable { public void bounce() { System.out.println("Basket Ball is bouncing"); } @Override public void inflate() { System.out.println("Basket Ball has been inflated"); } } // A tennis ball is bounceable but not inflatable class TennisBall implements Bounceable { public void bounce() { System.out.println("Tennis Ball is bouncing"); } } |
Usage
There are two methods – toss and inflate. The codes can pass BasketBall instances to both methods but can only pass TennisBall instances to the toss method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class BounceableInflatableDemo { public void toss(Bounceable bounceableItem) { bounceableItem.bounce(); } public void inflate(Inflatable inflatableItem) { inflatableItem.inflate(); } public static void main(String[] args) { BounceableInflatableDemo bounceableInflatableDemo = new BounceableInflatableDemo(); bounceableInflatableDemo.toss(new BasketBall()); bounceableInflatableDemo.inflate(new BasketBall()); bounceableInflatableDemo.toss(new TennisBall()); //bounceableInflatableDemo.inflate(new TennisBall()); } } |
The codes generate this output.
1 2 3 | Basket Ball is bouncing Basket Ball has been inflated Tennis Ball is bouncing |
If we uncomment the last time, we will get a compilation error. The error means that the inflate method cannot accept any class that does not implement the Inflatable interface. If we look at the TennisBall class, it implements the Bounceable interface, not the Inflatable interface – thus the error message “Incompatible types: TennisBall cannot be converted to Inflatable.”
In Rust – Generic Function With Generic Parameter That Implements A Trait
Rust Traits
Rust does not have interfaces. Instead, they have traits. Traits are no way similar to Java interfaces – just to be clear! In this section, we are essentially porting the Java codes to Rust.
1 2 3 4 5 6 7 | trait Bounceable { fn bounce(&self); } trait Inflatable { fn inflate(&self); } |
Rust Trait Implementations
First, we make BasketBall both Bouceable and Inflatable.
1 2 3 4 5 6 7 8 9 10 11 12 13 | struct BasketBall {} impl Bounceable for BasketBall { fn bounce(&self) { println!("Basket Ball is bouncing"); } } impl Inflatable for BasketBall { fn inflate(&self) { println!("Basket Ball has been inflated"); } } |
Second, we make TennisBall Inflatable only.
1 2 3 4 5 6 7 | struct TennisBall {} impl Bounceable for TennisBall { fn bounce(&self) { println!("Tennis Ball is bouncing"); } } |
Usage
We have generic functions that accept structs of T parameter type.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | fn toss<T:Bounceable>(b: T) { b.bounce(); } fn inflate<T:Inflatable>(i: T) { i.inflate(); } fn main() { toss(BasketBall {}); inflate(BasketBall{}); toss(TennisBall{}); //inflate(TennisBall{}); } |
The codes generate the following output.
1 2 3 | Basket Ball is bouncing Basket Ball has been inflated Tennis Ball is bouncing |
The codes are now very similar to the Java codes. We successfully ported the functionality to Rust.
If we try to uncomment the last time, we will have a compilation error. The error means that the inflate method cannot accept any struct that does not implement the Inflatable trait. If we look at the TennisBall struct, it implements the Bounceable trait, not the Inflatable trait – thus the error message “the trait Inflatable is not implemented for TennisBall.”
In summary, we can create generic functions in Rust that accepts any struct instances that implements a particular trait with the help of generics. Although we use Java codes for demonstration, Rust traits are, by no means, similar to Java interfaces.
We tested the codes using Rust 1.39.0.
Errata
- Changed
self to
&self. Using
&self is recommended to avoid
error[E0382] when calling other functions in the same instance. For example:12345678910111213141516171819...trait Bounceable {// Change to &selffn bounce(self);}trait Inflatable {// Change to &selffn inflate(self);}...fn main() {let basket_ball: BasketBall = BasketBall{};basket_ball.bounce();// Will caused "error[E0382]: use of moved value: `basket_ball`" if we do not use &selfbasket_ball.inflate();}