Function 在Rust中实际需要移动到函数的情况

Function 在Rust中实际需要移动到函数的情况,function,pointers,reference,rust,move,Function,Pointers,Reference,Rust,Move,我想确定在哪些一般(或特定)情况下不希望使用引用将对象传递到Rust中的函数中 我最感兴趣的是功能性(例如,创建函数是为了清理移动到其中的资源),但性能方面的原因也很有趣(例如,如果复制一个小结构比通过指针访问要快) 当然,这涉及到非复制类型 注:很清楚为什么在赋值中使用移动语义,以防止出现别名,例如,但在不会出现问题的函数中使用移动语义。有一些类似的情况,所有这些情况都与结构的生命周期有关,即,我正在编写Octavo(加密库),其中我们有一些散列函数(即SHA1): 类似上述情况的其他用法是生

我想确定在哪些一般(或特定)情况下不希望使用引用将对象传递到Rust中的函数中

我最感兴趣的是功能性(例如,创建函数是为了清理移动到其中的资源),但性能方面的原因也很有趣(例如,如果复制一个小结构比通过指针访问要快)

当然,这涉及到非复制类型


注:很清楚为什么在赋值中使用移动语义,以防止出现别名,例如,但在不会出现问题的函数中使用移动语义。

有一些类似的情况,所有这些情况都与结构的生命周期有关,即,我正在编写Octavo(加密库),其中我们有一些散列函数(即SHA1):

类似上述情况的其他用法是生成器模式:

PizzaBuilder::new()
    .add_sauce()
    .add_cheese()
    .add_topping(Topping::Tomato)
    .add_topping(Topping::Bacon)
    .add_topping(Topping::Jalapenio)
    .bake() // here we destroy PizzaBuilder as this setup isn't usable  anymore
            // you should create new PizzaBuilder for another pizza
            // (you cannot reuse dough once you bake pizza)

移动语义的一个应用是类型安全状态机。假设您有一个对象可以处于两种状态,未初始化和已初始化,并且从未初始化到已初始化会产生副作用,可能会失败。这自然由两种类型和一种转换方法建模,转换方法通过值接受第一种类型的对象并返回第二种类型的对象:

pub struct UninitFoo {
    ...
}

impl UninitFoo {
    pub fn new() -> UninitFoo { ... }
    pub fn configure_something(&mut self) { ... }
    pub fn configure_something else(&mut self) { ... }
    pub fn initialize(self) -> Result<InitFoo, SomeError> { ... }
}

pub struct InitFoo {
    ...
}

impl InitFoo {
    pub fn do_some_work(&mut self) { ... }
}
pub结构UninitFoo{
...
}
impl UninitFoo{
pub fn new()->UninitFoo{…}
pub fn configure_something(&mut self){…}
发布fn配置其他内容(&mut self){…}
pub fn initialize(self)->结果{…}
}
发布结构InitFoo{
...
}
impl InitFoo{
pub fn做一些工作(&mut self){…}
}

上面的例子确实是人为的,但我想你明白了。通过这种方式,您的类型集基本上形成了一个状态机,其中像
initialize()
这样的方法是状态之间的转换。

考虑一个转换函数
fn(T)->U
。如果引用
T
,则
U
上的所有操作都需要维护
T
中的不变量。这意味着不允许以任何方式销毁
T
。此外,调用者将负责将输入保持在适当的位置,以便
U
保持有效。一个很好的例子是
Vec::into_boxed_slice

另一个例子是移动其输入的函数。显然,如果没有令人担忧的语义,这无法接受
&mut
。例如
Vec::insert

另一个选项是当
&mut Trait:Trait
时。在这种情况下,使用
T:Trait
允许调用方决定是使用动态分派还是传递参数进行借用,这对易用性(双向)、速度和代码膨胀都有影响

另一个例子可能只是对普通情况下的方便语法的偏好,在这种情况下克隆是便宜的。例如
Vec
Index


如果您想限制在给定对象上调用函数的次数,还有一些令人信服的安全原因。最简单的例子是
drop
,您只能调用一次。一个非常奇特的例子是频道通信的“会话类型”。

如果复制一个小结构比通过指针访问要快的话,那就是。我投票将这个问题作为离题题结束,因为应该有一个明显的“最佳”答案,并问一些“原因列表”的问题倾向于开放式,并且所有答案都是同样有效的。(否则,对于一个酷的应用程序,在Google上搜索“Rust中的会话类型”,或者通常考虑使用这样一个系统对状态机进行编码:它保证你不会从一个过时的状态移动!)我只是想指出,这样的问题可能会受到r/rust或users.rust-lang.org的欢迎。@Shepmaster我就是这么想的。然而,这将是一个容易的优化,不是吗?如果所有内容都是不可变的,语义就不会改变。
//在这里,当摘要完成工作时,我们将使其无效。
——您能详细说明一下吗?还有,你的第二个例子。PizzaBuilder是如何销毁的?bake做什么呢?在大多数实现中,哈希引擎具有内部状态(即,
bool
),它会发出信号,指示是否已经计算了哈希(因为它将数据添加到消息中并执行一些更奇特的操作)。在生锈的情况下,我只是破坏了引擎,而不是那个额外的内部状态。关于第二个问题,
PizzaBuilder::bake(self)->Pizza
是烘焙(创建)您的比萨饼的方法。好的,那么销毁/使这些对象无效的机制是通过移动获取
self
的方法吗?是的。请看@Veedrac的回答,其中他指出,当您将一个对象更改为另一个对象时,您希望使旧对象无效以保持其安全(将slice更改为Vec会使slice中的值无效,将
Result
更改为
选项
也会这样做,等等)。一个很好的例子是
Vec::into_boxed_slice
你能详细说明吗?@ JCO考虑如果<代码> VEC::然后,您无法将装箱的切片移动到可能比
Vec
更长寿的对象中,甚至无法将
Vec
移动到某个地方以延长其寿命。此外,当
被销毁时,它将释放其内存。。。使
Vec
无效!你可以通过将
放入_boxed_slice
中,将
Vec
保留为空来解决这个问题,但这似乎是一个获得理智行为的奇怪方法。另外,
mem::replace(&mut-vec,vec![])。如果您真的需要它,into_-boxed_-slice()
也会做同样的事情。
pub struct UninitFoo {
    ...
}

impl UninitFoo {
    pub fn new() -> UninitFoo { ... }
    pub fn configure_something(&mut self) { ... }
    pub fn configure_something else(&mut self) { ... }
    pub fn initialize(self) -> Result<InitFoo, SomeError> { ... }
}

pub struct InitFoo {
    ...
}

impl InitFoo {
    pub fn do_some_work(&mut self) { ... }
}