如何在单个函数中接受 &str、String 和 &String?

46

我想编写一个单独的函数,它接受一个 &str,一个 String 和一个借用的 &String。我已经编写了以下两个函数:

fn accept_str_and_ref_string(value: &str) {
    println!("value: {}", value);
}

fn accept_str_and_string<S: Into<String>>(value: S) {
    let string_value: String = value.into();
    println!("string_value: {}", string_value);
}

fn main() {
    let str_foo = "foo";
    let string_foo = String::from("foo");

    accept_str_and_ref_string(str_foo);
    accept_str_and_ref_string(&string_foo);

    accept_str_and_string(str_foo);
    accept_str_and_string(string_foo);
}

有没有可能实现一个函数,让我可以这样做:

accept_all_strings(str_foo);
accept_all_strings(&string_foo);
accept_all_strings(string_foo);

2
看起来自 Rust 1.35.0 起,Into<String> 变体也适用于 &String - Matthias
是的,你说得对,我已经在当前的Rust 1.36版本中进行了测试。 - Ringo Leese
3个回答

65
你可以使用AsRef<str>特性:
// will accept any object that implements AsRef<str>
fn print<S: AsRef<str>>(stringlike: S) {
    // call as_ref() to get a &str
    let str_ref = stringlike.as_ref();

    println!("got: {:?}", str_ref)
}

fn main() {
    let a: &str = "str";
    let b: String = String::from("String");
    let c: &String = &b;

    print(a);
    print(c);
    print(b);
}

print函数将支持任何实现了AsRef<str>的类型,包括&strString&String


@trentcl 一个 String 不会被复制,除非你使用引用,否则它的所有权将传递给该方法。 - Frxstrem
1
我的错误,我的大脑把OP的示例主体和你的签名搞混了。 - trent
1
如果函数最终需要一个拥有所有权的字符串而不是一个引用呢? - malthe
8
个人而言,通常我只使用stringlike.as_ref().to_string(),因为我喜欢在需要拥有或借用字符串时具有相同的签名。我认为如果参数已经是拥有的字符串,那可能有点低效,所以如果你想避免这种情况,可以使用其他特性,比如S: Into<String> - Frxstrem

11
自从2015年的rust版本以来,accept_all_strings函数按照OP的要求与Into<String>特质一起正常工作。
fn accept_all_strings<S: Into<String>>(value: S) {
    let string_value = value.into();
    println!("string_value: {string_value}");
}

感谢Jeremy Chone的建议。现在函数签名可以简化为这样:
fn accept_all_strings(value: impl Into<String>) {
    let string_value = value.into();
    println!("string_value: {string_value}");
}

游乐场

关于 String&strVec<u8>&[u8] 之间的转换,可以在这里找到很好的概述这里...


1
是的,这个函数接受三种类型的字符串,并且只在必要时进行分配。看起来 OP 已经设置了选项 accept_str_and_string,也许认为您不能传递 &String。顺便说一下,现在我们可以使用print(stringlike: impl Into<String>) { ... }这样会稍微简短一些。 - Jeremy Chone
@Chone 你提到了print宏的新功能,可以直接从变量中获取参数?我字面上理解 OP 希望得到accept_all_strings函数。依我之见,把用户函数命名为print是具有误导性和容易出错的。而且我不确定选择Into还是AsRef只是口味问题... - Kaplan
抱歉,我的错误,我不是想引用“print”宏。应该是你提到的“accept_all_strings(val: impl Into<String>)”。 - Jeremy Chone
我认为AsRef和Into之间存在差异。前者只能获取引用;如果您想要所有权,则必须进行新的分配。当可能时,Into将为您提供所有权而无需分配。对于我的构建器/结构,我倾向于使用Into,因为大多数情况下,我想要String的所有权,但不想要求调用者在引用上添加.to_string()。 - Jeremy Chone
@Kaplan,是的,如果我想在输入的字符串/字符中解析/查找某些内容,并返回该数字/部分。 - DimanNe
显示剩余3条评论

0

我通常使用:impl ToString

在文档中 有很多实现该特质的东西,包括 stru8 等。

这还允许调用者传递任何实现了 std::fmt::Display 的内容。

fn accept_all_strings(value: impl ToString) {
    let string_value = value.to_string();
    println!("string_value: {string_value}");
}

如果你调用.to_string(),它会分配一个新的字符串,这可能会对性能产生影响。即使String::to_string()也调用了.to_owned(),后者又调用了.clone()

但好的一面是,你完全拥有输出结果。没有共享。


如果你只是想打印它,而不需要一个实际的字符串值,你可以使用以下方法:
fn accept_all_strings(value: impl std::fmt::Display) {
    println!("value: {value}");
}

如果你想对打电话的人更友善一些,你可以只要求调试(这是相当常见的),然后使用:#?来美化打印输出。我猜这只是一个小建议。
fn accept_all_strings(value: impl Debug) {
    println!("value: {value:#?}");
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接