I am completely new to rust. I created a program to store information about persons to get in touch with the language:
person.rs
pub struct Person {
firstname: String,
lastname: String,
pub age: u8
}
impl Person {
pub fn new(firstname: String, lastname: String, age: u8) -> Person {
return Person { firstname: firstname, lastname: lastname, age: age };
}
pub fn from_str(firstname: &str, lastname: &str, age: u8) -> Person {
return Person::new(firstname.to_string(), lastname.to_string(), age);
}
pub fn name(&self) -> String {
return self.firstname.to_owned() + " " + &self.lastname;
}
}
main.rs
mod person;
use person::Person;
fn main() {
let me = Person::from_str("John", "Doe", 42);
println!("{} is {} years old.", me.name(), me.age);
}
I understand, that there is no function overloading in rust.
However, I have read about a concept called "traits", which I have not fully understood.
Do they behave similar to templates in C++ (I’m also not a C++ pro)?
Is there another way, e.g. using traits, so that I can write Person::new() to accept either String or &str?
I tried this to no avail:
person.rs
pub struct Person {
firstname: String,
lastname: String,
pub age: u8
}
impl Person {
/*
pub fn new(firstname: String, lastname: String, age: u8) -> Person {
return Person { firstname: firstname, lastname: lastname, age: age };
}
pub fn from_str(firstname: &str, lastname: &str, age: u8) -> Person {
return Person::new(firstname.to_string(), lastname.to_string(), age);
}
*/
pub fn new<T>(firstname: &T, lastname: &T, age: u8) -> Person {
return Person::new(firstname.to_string(), lastname.to_string(), age);
}
pub fn name(&self) -> String {
return self.firstname.to_owned() + " " + &self.lastname;
}
}
main.rs
mod person;
use person::Person;
fn main() {
let me = Person::new("John", "Doe", 42);
println!("{} is {} years old.", me.name(), me.age);
}
Resulting in:
$ rustc main.rs -o main
error[E0599]: the method `to_string` exists for reference `&T`, but its trait bounds were not satisfied
--> person.rs:20:38
|
20 | return Person::new(firstname.to_string(), lastname.to_string(), age);
| ^^^^^^^^^ method cannot be called on `&T` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`T: std::fmt::Display`
which is required by `T: ToString`
`&T: std::fmt::Display`
which is required by `&T: ToString`
error[E0599]: the method `to_string` exists for reference `&T`, but its trait bounds were not satisfied
--> person.rs:20:60
|
20 | return Person::new(firstname.to_string(), lastname.to_string(), age);
| ^^^^^^^^^ method cannot be called on `&T` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`T: std::fmt::Display`
which is required by `T: ToString`
`&T: std::fmt::Display`
which is required by `&T: ToString`
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> main.rs:6:14
|
6 | let me = Person::new("John", "Doe", 42);
| ^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `str`
note: required by a bound in `Person::new`
--> person.rs:19:16
|
19 | pub fn new<T>(firstname: &T, lastname: &T, age: u8) -> Person {
| ^ required by this bound in `Person::new`
help: consider relaxing the implicit `Sized` restriction
--> person.rs:19:17
|
19 | pub fn new<T: ?Sized>(firstname: &T, lastname: &T, age: u8) -> Person {
| ++++++++
error: aborting due to 3 previous errors
Some errors have detailed explanations: E0277, E0599.
For more information about an error, try `rustc --explain E0277`.
>Solution :
Rust has generics, not templates. Generics can be bounded by traits, which provide a system for standard interfaces.
In your case, what you need is a trait that will give you a String from either a String or a &str. You have a few options, but I’d choose Into<String>.
You can do this:
pub fn new(firstname: impl Into<String>, lastname: impl Into<String>, age: u8) -> Person {
Person {
firstname: firstname.into(),
lastname: lastname.into(),
age
}
}
Essentially what this says is:
- new is a function
- which takes three arguments
- the first two args can be any type that implements
Into<String> - the last argument must be a
u8
Into<T> is a standard trait in Rust meant for the use in conversions. It defines an associated fn into(self) -> T that takes the given object by value and returns the target T.
Into is really just the complementary helper trait to From<T> which has an associated fn from(other: T) -> Self that takes some other type and returns the type you want.
From has a reflexive implementation, which essentially means that for any type, you can do T::from(x) where x is of type T. This also applies to Into, so any type can be converted into itself, which is why String will work when the compiler expects Into<String>.