Rust技巧:AsRef<str> 和 Into<String>

686次阅读  |  发布于1年以前

Rustd的字符串类型有两种不同变体:&str 和 String。它们的不同之处在于&str是对String或str的引用,String是动态分配的字符串对象。

use std::time::{SystemTime, UNIX_EPOCH};

fn time_stamp(msg: &str) -> String {
    let time = SystemTime::now()
        .duration_since(UNIX_EPOCH).unwrap().as_millis();
    time.to_string() + ": " + msg
}

fn main() {
    let s1 = "msg as &str";
    let s2 = String::from("msg as String");
    println!("{}", time_stamp(s1));
    println!("{}", time_stamp(&s2)); // 注意额外的' & '字符
}

由于函数time_stamp()接受&str类型作为形参,因此必须显式地将String类型转换为带有&字符的字符串,否则,我们将得到Rust编译错误。如果Rust可以自动处理这两种类型岂不是很好?这是可以的!不需要指定具体的参数类型&str,我们可以指定一个实现AsRef trait的类型:

fn time_stamp(msg: impl AsRef<str>) -> String {
    let time = SystemTime::now()
        .duration_since(UNIX_EPOCH).unwrap().as_millis();
    time.to_string() + ": " + msg.as_ref()
}

fn main() {
    let s1 = "msg as &str";
    let s2 = String::from("msg as String");
    println!("{}", time_stamp(s1));
    println!("{}", time_stamp(s2)); // 去掉了' & '字符
}

现在,我们可以将任何实现AsRef的类型传递给函数,比如&str或String。这在为库设计API时特别有用,因为它使客户端代码更容易编写。

不过要注意,也不是在任何情况下都使用这个技巧。考虑下面的函数,你觉得有什么问题吗?

fn concat(msg1: impl AsRef<str>, msg2: impl AsRef<str>) -> String {
    msg1.as_ref().to_owned() + "; " + msg2.as_ref()
}

当为msg1提供的类型已经是一个String时,函数concat正在做不必要的工作,因为它正在调用msg1上的to_owned()方法。理想情况下,如果msg1已经是String类型,我们希望重用它。为此,你可以使用另一个Into的trait:

fn concat(msg1: impl Into<String>, msg2: impl AsRef<str>) -> String {
    msg1.into() + "; " + msg2.as_ref()
}

总之,如果首选类型是&str,使用AsRef trait绑定,如果首选类型是String,使用Into trait绑定。此外,只要注意不要像前面的例子那样执行不必要的工作,就不会有性能损失!

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8