创建接受字符串或 &str 的灵活构造函数方法



我对 rust 完全陌生。我创建了一个程序来存储有关人员的信息,以便与该语言取得联系:

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);
}

我知道,rust 中没有函数重载。 但是,我读过一个叫做"特质"的概念,我还没有完全理解。 它们的行为是否类似于C++中的模板(我也不是C++专业人士)? 有没有另一种方法,例如使用特征,以便我可以编写Person::new()来接受String&str

我试过这个无济于事:

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);
}

结果是:

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

Rust 有泛型,而不是模板。泛型可以由特征限制,这些特征为标准接口提供了一个系统。

在您的情况下,您需要的是一种特质,可以让您从String&str中获得String。你有几个选择,但我会选择Into<String>.

您可以这样做:

pub fn new(firstname: impl Into<String>, lastname: impl Into<String>, age: u8) -> Person {
Person {
firstname: firstname.into(),
lastname: lastname.into(),
age
}
}

从本质上讲,这说的是:

  • 新是一个函数
  • 这需要三个参数
  • 前两个参数可以是实现Into<String>的任何类型
  • 最后一个参数必须是u8

Into<T>是 Rust 中的一个标准特征,用于转换。它定义了一个关联的fn into(self) -> T,该按值获取给定对象并返回目标T

Into实际上只是From<T>的补充帮助程序特征,它具有关联的fn from(other: T) -> Self,该采用其他类型并返回所需的类型。

From有一个反身实现,这本质上意味着对于任何类型,您都可以在x属于T类型的地方执行T::from(x)。这也适用于Into,因此任何类型都可以into自身转换,这就是为什么String在编译器期望Into<String>时工作的原因。

你可以接受impl Into<String>

pub fn new(firstname: impl Into<String>, lastname: impl Into<String>, age: u8) -> Person {
Person {
firstname: firstname.into(),
lastname: lastname.into(),
age: age,
}
}

参数位置impl Trait是声明参数是泛型的简短方法。您也可以使用显式命名的泛型类型写出函数签名:

pub fn new<F, L>(firstname: F, lastname: L, age: u8) -> Person
where
F: Into<String>,
L: Into<String>,

过多详细解释Into的使用:

  1. IntoFrom的反面,这要归功于这种一揽子实现:

    impl<T, U> const Into<U> for T
    where
    U: ~const From<T>,
    {
    fn into(self) -> U {
    U::from(self)
    }
    }
    

    只要存在impl From<T> for U,匹配的impl Into<U> for T也会存在。

  2. 标准库为String提供了多种这样的实现:

    impl From<char> for String
    impl From<Box<str>> for String
    impl From<&str> for String
    impl From<&String> for String
    impl From<&mut str> for String
    impl From<Cow<'_, str>> for String
    

    多亏了impl From<&str> for String,您可以从&str构建String

  3. 此外,所有T都有一个全面的实现:

    impl<T> From<T> for T {
    fn from(t: T) -> T {
    t
    }
    }
    

    任何类型都可以简单地转换为自身。这意味着impl From<String> for String也可以作为零成本转换使用。

最新更新