将元素添加到不可变向量 Rust

Ale*_*lex 3 recursion functional-programming vector immutability rust

我正在尝试利用函数式编程和递归在 Rust 中创建一个用户输入验证函数。如何返回一个不可变向量,其中一个元素连接到末尾?

fn get_user_input(output_vec: Vec<String>) -> Vec<String> {
    // Some code that has two variables: repeat(bool) and new_element(String)
    if !repeat {
        return output_vec.add_to_end(new_element); // What function could "add_to_end" be?
    }
    get_user_input(output_vec.add_to_end(new_element)) // What function could "add_to_end" be?
}
Run Code Online (Sandbox Code Playgroud)

还有其他所有功能的函数:
push将可变向量添加到可变
append向量 将元素添加到可变向量的末尾
concat将不可变向量添加到不可变向量
???将元素添加到不可变向量的末尾

我能够做到的唯一解决方案开始工作正在使用:

[write_data, vec![new_element]].concat()
Run Code Online (Sandbox Code Playgroud)

但这似乎效率低下,因为我只为一个元素创建一个新向量(因此大小在编译时已知)。

cdh*_*wie 19

您将 Rust 与一种只引用对象的语言混淆了。在 Rust 中,代码可以拥有对象的独占所有权,因此您无需小心地修改可以共享的对象,因为您知道该对象是否是共享的。

例如,这是有效的 JavaScript 代码:

const a = [];
a.push(1);
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为a不包含数组,它包含对数组的引用。1 防止consta重新指向不同的对象,但它不会使数组本身不可变。

因此,在这些类型的语言中,纯函数式编程试图避免改变任何状态,例如将一个项目推送到作为参数的数组上:

function add_element(arr) {
  arr.push(1); // Bad! We mutated the array we have a reference to!
}
Run Code Online (Sandbox Code Playgroud)

相反,我们做这样的事情:

function add_element(arr) {
  return [...arr, 1]; // Good! We leave the original data alone.
}
Run Code Online (Sandbox Code Playgroud)

考虑到你的函数签名,你在 Rust 中所拥有的,是一个完全不同的场景! 在您的情况下,output_vec由函数本身拥有,程序中的任何其他实体都无法访问它。因此,如果这是您的目标,则没有理由避免对其进行突变:

fn get_user_input(mut output_vec: Vec<String>) -> Vec<String> {
//       Add mut  ^^^
Run Code Online (Sandbox Code Playgroud)

您必须记住,任何非引用都是拥有的价值。&Vec<String>将是对其他人拥有的向量的不可变引用,但是Vec<String>是此代码拥有的向量并且其他人无法访问。

不相信我?下面是一个简单的损坏代码示例,演示了这一点:

fn take_my_vec(y: Vec<String>) { }

fn main() {
    let mut x = Vec::<String>::new();
    
    x.push("foo".to_string());
    
    take_my_vec(x);
    
    println!("{}", x.len()); // E0382
}
Run Code Online (Sandbox Code Playgroud)

该表达式x.len()会导致编译时错误,因为向量x移至函数参数中,而我们不再拥有它。

那么为什么该函数不应该改变它现在拥有的向量呢?调用者不能再使用它。


总而言之,函数式编程在 Rust 中看起来有点不同。在其他无法传达“我正在给你这个对象”的语言中,你必须避免改变给定的值,因为调用者可能不希望你更改它们。在 Rust 中,谁拥有一个值是明确的,并且这个参数反映了:

  • 参数是值 ( Vec<String>) 吗?该函数现在拥有该值,调用者将其放弃并且不能再使用它。如果需要的话,改变它。
  • 参数是不可变引用 ( &Vec<String>) 吗?该函数不拥有它,并且无论如何也不能改变它,因为 Rust 不允许这样做。你可以克隆它并变异克隆。
  • 参数是可变引用 ( &mut Vec<String>) 吗?调用者必须显式地为函数提供可变引用,因此授予函数更改它的权限——但该函数仍然不拥有该值。该函数可以改变它、克隆它,或者两者兼而有之——这取决于该函数应该做什么。

如果您按值获取参数,那么无论mut出于何种原因需要更改它,都没有理由不这样做。请注意,这个细节(函数参数的可变性)甚至不是函数公共签名的一部分,因为它不是调用者的事。他们把这个东西送人了。


请注意,对于具有类型参数(如Vec)的类型,其他所有权表达式也是可能的。以下是一些示例(这不是详尽的列表):

  • Vec<&String>:您现在拥有一个向量,但您不拥有String它包含引用的对象。
  • &Vec<&String>:您被授予对字符串引用向量的只读访问权限。例如,您可以克隆此向量,但仍然无法更改字符串,只能重新排列它们。
  • &Vec<&mut String>:您被授予对可变字符串引用向量的只读访问权限。您无法重新排列字符串,但可以更改字符串本身。
  • &mut Vec<&String>:与上面类似但相反:您可以重新排列字符串引用,但不能更改字符串。

1一个好的思考方法是 JavaScript 中的非原始值始终是 的值Rc<RefCell<T>>,因此您将传递具有内部可变性的对象的句柄。const只会让事情Rc<>变得不可变。

  • @IvenMarquardt 不,该值被移动到函数参数中,并且调用者不再拥有它。当函数返回时,该值将被销毁,除非它被移动到其他地方(例如通过返回)。在我的示例代码中,在调用“take_my_vec(x)”之后,局部变量“x”可以再次被视为“未初始化”。(由于“x”是“mut”,因此您可以为其分配另一个向量,然后再次使用该变量,但以前在“x”中的向量已转移到函数参数中。) (2认同)
  • @IvenMarquardt 有一个内置函数`std::mem::drop()`。此函数的目的是销毁不再需要的值,释放可能与其关联的任何内存分配或其他资源(文件/网络句柄等)。它是如何做到这一点的?这个函数的定义字面意思是“pub fn drop&lt;T&gt;(_x: T) { }”。这里没有魔法——该值被破坏,因为您将它移动到函数参数,并且该函数立即返回,破坏了该值,因为它现在是所有者,并且拥有的变量超出了范围。 (2认同)