Reputation: 137
I'm currently learning rust and stumbled across a problem I'm not sure how to solve.
I want to have a type (called Guess
), thats just a i32
limited to a certain number range (between 1 and 100 in example below). My current solution is to use a type alias implementing a new
function to enforce the number range. Still it can be created through normal initialization, without using Guess::new()
, because there are no private fields like in a struct.
Can this be prevented? Also a direct change of the value should be prevented too.
Code example (playground):
type Guess = i32;
trait G {
fn new(val: i32) -> Guess;
}
impl G for Guess {
fn new(val: i32) -> Guess {
if val < 1 || val > 100 {
panic!("Only guesses between 1 and 100 are allowed!");
}
val
}
}
fn main() {
let guess: Guess = 23; // should not be allowed
let guess: Guess = Guess::new(42); // only this should be allowed
println!("Guess this number: {}", guess);
}
Upvotes: 2
Views: 639
Reputation: 26136
As others have said, what you want to do is use the "newtype" pattern, which is basically wrapping your type into another:
struct Guess(i32);
impl Guess {
fn new(val: i32) -> Self {
assert!(val >= 1 && val <= 100,
"Only guesses between 1 and 100 are allowed!");
Self(val)
}
}
fn main() {
//let guess: Guess = 23; // this is an error now
let guess: Guess = Guess::new(42); // fine
println!("Guess this number: {}", guess.0);
}
The main different is that users will need to use guess.0
.
A few other improvements too:
Self
avoids repetitions of the typename.assert!
for if
+ panic!
.Upvotes: 1
Reputation: 43782
A type
never gives you any additional privacy/guarantees/invariants. What type
defines is strictly an alias: "use this name as another name for this existing type". In your code as it is, Guess
and i32
can be used interchangeably.
In order to implement the range restriction you want, you must define a new type (not just a new name for an existing type) using struct
.
Upvotes: 1