Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Create flexible constructor method accepting either String or &str

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

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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>.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading