Constrain Struct Generic Field in Rust
Table of Contents
It is a quite common need to constrain struct generic field to certain types in Rust. We usually have two different ways to implement this. Trait or dispatching each of them by ourselves.
In this post, I am going to show you how to implement this. Therefore, this post should be meant for the “beginners”. This post is gonna show you from the basic to the final working version.
It is recommended to read the most fundamental basics from this article. First part of codes are based on it.
Demands
We need a struct which has some fields with generics, but we need some restrictions as well.
Let’s call it by Container
, which contains something. But we don’t know what type it is, so generics are necessary.
First, in the form of code, we define struct like this:
Second, we would like to constrain types to certain types, like integer, float or custom structs.
Let’s name a custom struct, call Color
.
Therefore, for exmaple, T can only be i32
, f64
and Color
. Nothing else.
Rust compiler now knows the specific forms of Container, which will expand codes into detailed ones.
Two ways to constrain
Dispatching
Original
From this article, you get these codes:
If you know some very basic knowledge about generics, you should know what it is doing.
Anyway, short explanation: Only Color
container works because it has its own implementation for Color
container only. Integer type or float type won’t work due to empty of implementation.
So, it gets what we want. Some constraints on generic types.
Modification
Let’s modify codes to make it more elaborate for future purpose.
Print some details to make it look like workable and provable.
As you can see, we need to dispatch three types manually in order to make this work. Three times duplication as well.
But it will work anyway. Its output:
Try to comment out implementation for f64. See what it happens.
error: no method named `print` found for `>`
|
= note: the method was found for
- ` `
- ` `
For more information about this error, try `rustc --explain E0599`.
Rust compiler complains about Container<{float}>
because we do not implement them. Therefore, we are only allow to use Color
and i32
type.
One More Step: Remove Deuplication
The problem is that we still needs to duplicate exact codes for 3 times. We can remove that duplication by just implementing for all types. (Yes, we do not consider restrictions for now. Deal with it later.)
Check codes:
use Debug;
// `Debug` to set bounds for generics
We just implement for all types by using impl<T: Debug> Container<T> { }
. Therefore, rust compiler will know expand codes automatically by using impl<T>
.
Trait & Bounds
However, in previous section we cannot set constraints on generic type just for convenience.
There is a solution for this. Traits and bounds.
The Book says traits can be used to define shared behaviors in an abstract way. The examples from The Book can demonstrate how it works precisely. (NewsArticle and Tweet can share the same behavior, which is summary from Summary trait.)
However, it can also be used for dispatching, which is already a common sense for veterans. In the following secions, you may learn it from some demos.
- Make constraints
All types must follow constraints, which is called ContainerTrait
.
- Define Trait/Boundary
// you can alow use empty trait only
// `foo` is used to show some details
- Define what boundary it is Explanation: Traits act like the void, which is nothing there. Rust compiler would never know when it is the void, right? So, we need to add some actual stuff in that “void” trait for the compiler.
Let’s add then.
So, other types does not satisfy the bound ContainerTrait, such as u32. These are basic constraints for that trait.
- Full picture
That’s it. Let’s see the big picture and check whether it works or not.
use Debug;
It still works:
If we comment out the implementation for f64
, check the compiler:
error: the : ContainerTrait` is not satisfied
-/fun_3_trait.rs:48:17
|
48 | let inst3 = Container ;
}`
|
= help: the following implementations were found:
<i32 as ContainerTrait>
note: required by a bound in `Container`
-/fun_3_trait.rs:35:21
|
35 | `
-/fun_3_trait.rs:52:17
|
52 | inst3.field.foo; // Won't work - "the trait `ContainerTrait` is not implemented for `{float}`"
| ^^^
Some errors have detailed explanations: E0277, E0689.
For more information about an error, try `rustc --explain E0277`.
Rust compiler says no implementation for {float}
. That’s it. Constraints!
One More: Struct Implementation with Trait
One more step: define implementation for that struct for usable demo.
use Debug;
// New here
Now, the real-life example should be complete.
If you find anything wrong, feel free to contact me.