如何将临时字符串转换为&str?

6

我希望将使用 format! 宏创建的 String 转换为 &str ,并使用 let 绑定将其赋值给一个变量:

fn main() {
    let my_bool = true;
    let other = String::from("my_string");
    let result = if my_bool {
        format!("_{}", other).as_str()
    } else {
        "other"
    };

    println!("{}", result);
}

(Rust Playground)

当我这样做时,编译器会抱怨临时的 String 值在语句结束时被释放(从我的理解来看),这意味着我无法动态创建 &str:

error[E0716]: temporary value dropped while borrowed
 --> src/main.rs:5:9
  |
4 |     let result = if my_bool {
  |         ------ borrow later stored here
5 |         format!("_{}", other).as_str()
  |         ^^^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
6 |     } else {
  |     - temporary value is freed at the end of this statement
  |

我一直在努力理解Rust的生命周期系统,但我真的无法理解其中的某些内容。Rust建议以下:

  = note: consider using a `let` binding to create a longer lived value

我将 format!("_{}", other) 包裹在一个 let 绑定中:

fn main() {
    let my_bool = true;
    let other = String::from("my_string");
    let result = if my_bool {
        let s = format!("_{}", other);
        s.as_str()
    } else {
        "other"
    };

    println!("{}", result);
}

但是它似乎不能解决问题,因为当我对此绑定调用as_str()时,它仍然抱怨借用的值没有足够长的生命周期:

error[E0597]: `s` does not live long enough
 --> src/main.rs:6:9
  |
4 |     let result = if my_bool {
  |         ------ borrow later stored here
5 |         let s = format!("_{}", other);
6 |         s.as_str()
  |         ^ borrowed value does not live long enough
7 |     } else {
  |     - `s` dropped here while still borrowed

当我省略整个if语句时,它可以正常工作,但我不想这样做,因为这将在原始代码库中引起很多麻烦。

此外,这似乎有点像逃避责任,因为那样我仍然不知道为什么会失败。

我该如何系统地解决这个问题?


1
你为什么需要一个 str?如果你将其转换为 str,那么谁负责拥有该内存呢?https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c7d3b896cb0865e70f0ce501492de439 运行得很好。 - loganfsmyth
@loganfsmyth 需要 str 的原因是它是匹配语句的一部分,该语句返回str,在这种情况下,我希望可以返回 str 而不必在每个匹配 case 中都使用 String::from(...) 来进行包装。 - Joël Abrahams
5
&str 是一个借用值,因此为了返回一个 &str,被借用的内存需要存在某个地方。如果你的 match 必须返回一个 &str,那么你显式地限制自己只能返回在 match 运行之前已经存在的借用值,这意味着你不能在其中创建一个新的 String - loganfsmyth
@loganfsmyth 哦,那样更有意义。 - Joël Abrahams
您的问题已被标记为重复问题(原本是一个独立的回答)。链接如下:https://play.integer32.com/?version=stable&mode=debug&edition=2018&gist=e28770b663ae828f1bbdd5814f676d6a - trent
1个回答

10

&str是一种借用字符串,因此您不能从临时String中获取一个,否则引用将超过它所绑定的值。

有两个解决方法来缓解这个问题:


您可以创建一个新变量来存储if范围之外的String

fn main() {
    let my_bool = true;
    let other = String::from("my_string");
    
    let result;
    let result = if my_bool {
        result = format!("_{}", other);
        result.as_str()
    } else {
        "other"
    };

    println!("{}", result);
}

你可以使用 Cow 类型来实现你想要的功能:
use std::borrow::Cow;

fn main() {
    let my_bool = true;
    let other = String::from("my_string");
    let result = if my_bool {
        Cow::Owned(format!("_{}", other))
    } else {
        Cow::Borrowed("other")
    };

    assert_eq!("_my_string", result);
}

Cow(代表写时复制)是一个枚举类型,其可能拥有或借用数据。在这种情况下,result 的类型为 Cow<str>

您可以通过以下方式简化表示:

let result = if my_bool {
    format!("_{}", other).into()
} else {
    Cow::Borrowed("other")
};

或(风格问题):
let result: Cow<str> = if my_bool {
    format!("_{}", other).into()
} else {
    "other".into()
};

1
我鼓励将此答案移动/添加到重复问题中。 - Shepmaster
.as_str() 函数有什么问题吗? - alexzander
@alexzander,这没有任何问题。问题在于将对变量的引用泄露到变量作用域之外:在if作用域中创建的s变量在该作用域结束时被销毁。因此,如果OP的代码已经编译过,那么result变量将指向已释放的内存,这是不安全的。 - Boiethios
你的第一个解决方案是错误的。你正在改变一个不可变的值。result需要是mut,这样它才能正常工作。 - Santiago Alejandro Torres Arag
@SantiagoAlejandroTorresArag 我只是复制/粘贴了我的代码并尝试编译它,它按原样完美地工作。也许让你困惑的是我使用的变量遮蔽。看看这个没有遮蔽的版本,也许更清晰:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d5f20e25108bd3c60c684574ed2341cc - Boiethios

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