重新绑定一个变量,使其在`match`声明中使用时具有不同的类型。

huangapple go评论57阅读模式
英文:

Rebind a variable to a different type while used in the `match` declaration

问题

我正在使用Rust编写代码,使用类型状态模式来创建一个API和一个CLI。

我有一个名为VendingMachine<S>的结构体,其中S是一个表示当前状态的零大小类型(例如,VendingMachine<Start>VendingMachine<Payment>VendingMachine<Checkout>等)。

我想让我的自动售货机在循环中运行,并匹配每个状态到一些代码:

let mut machine = VendingMachine::new::<Start>(/*...*/);
loop {
    match machine.state {
        Start => {/* 做一些事情,然后使用某些过渡方法更改`machine`的类型为VendingMachine<Payment> */},
        Payment => {/* 做一些其他事情,然后使用其他过渡方法更改`machine`的类型为VendingMachine<Checkout> */},
        ...
    }
}

然而,编译器拒绝了这段代码 - 如果我在匹配的分支中重新绑定machine,例如这样:

let machine = machine.to_payment();

其中.to_payment() 具有以下签名:

fn to_payment(self) -> VendingMachine<Payment>

然后我会得到“使用了已移动的值:machine.state”错误,因为“machine由于前一次循环中的to_payment()方法调用而被移动”。

我该如何实现我需要的行为?

英文:

I am writing Rust code with a typestate pattern to create an API and a CLI for it.

I have a struct VendingMachine&lt;S&gt; where S is a ZST representing my current state (e.g. VendingMachine&lt;Start&gt;,VendingMachine&lt;Payment&gt;,VendingMachine&lt;Checkout&gt; etc.)

I want to have my vending machine to run in a loop and match each state to some code:

let mut machine = VendingMachine::new::&lt;Start&gt;(/*...*/);
loop {
    match machine.state {
        Start =&gt; {/* do something and then change the type of `machine` to VendingMachine&lt;Payment&gt; using some transitioning method */},
        Payment =&gt; {/* do something else and then change the type of `machine` to VendingMachine&lt;Checkout&gt; using some other transitioning method */},
        ...
    }
}

However, the compiler rejects this code - if I rebind `machine` in a match's arm to the result of a transition like this:

let machine = machine.to_payment();

where .to_payment() has this signature:

fn to_payment(self) -&gt; VendingMachine&lt;Payment&gt;

Then I get the "use of moved value: `machine.state`" error because "`machine` moved due to this [to_payment()] method call, in previous iteration of loop"

How do I implement the behaviour I need?

答案1

得分: 2

没有办法改变绑定的类型,在Rust中这是不可能的,因为在编译之后所有的类型都被遗忘了,如果类型是唯一不同的,那么你就无法区分VendingMachine<Start>VendingMachine<Payment>。为了能够区分它们,你必须使用在运行时可以区分的东西,比如枚举。

英文:

There is no way to change the type of a binding, this is impossible in Rust because after compilation all types are forgotten and if the type was all that's different, then you can't distinguish VendingMachine&lt;Start&gt; from VendingMachine&lt;Payment&gt;. To be able to separate them you have to use something that can be distinguished at runtime like an enum.

答案2

得分: 1

Rust是一种静态类型的语言,这意味着在运行时无法更改变量的类型。可以通过创建同名的新变量来隐藏(或“遮蔽”)一个变量:

let a: i32 = 42;
let a: String = "foo".to_string();

但这是_两个不同的变量_,第一个变量在创建第二个变量之后仍然存在,如下所示:

let a: i32 = 42;
let p = &a;
let a: String = "foo".to_string();
println!("{}", a);    // 输出 "foo"
println!("{}", p);    // 输出 "42"

Playground

原始的 a 仍然存在,并且甚至在第二个变量创建后仍然可以通过引用间接访问。

对于您的用例,如果您不想使用enum来防止无效状态转换,可以使用一个特性对象(trait object)代替:

use std::fmt::Debug;

trait State: Debug {
    fn process(self: Box<Self>) -> Box<dyn State>;
    fn is_finished(&self) -> bool { false }
}

#[derive(Debug)]
struct Start {}
impl State for Start {
    fn process(self: Box<Self>) -> Box<dyn State> {
        /* 做一些事情然后转移到下一个状态 */
        Box::new(InProgress {})
    }
}

#[derive(Debug)]
struct InProgress {}
impl State for InProgress {
    fn process(self: Box<Self>) -> Box<dyn State> {
        /* 做一些事情然后转移到下一个状态 */
        Box.new(Done {})
    }
}

#[derive(Debug)]
struct Done {}
impl State for Done {
    fn process(self: Box<Self>) -> Box<dyn State> {
        self
    }
    fn is_finished(&self) -> bool { true }
}

fn main() {
    let mut s: Box<dyn State> = Box::new(Start {});
    while !s.is_finished() {
        println!("当前状态: {:?}", s);
        s = s.process();
        println!("转移到状态: {:?}", s);
    }
    println!("最终状态: {:?}", s);
}

Playground

英文:

Rust is a statically typed language, which means that there is no way to change the type of a variable at runtime. It is possible to hide (or shadow) a variable by creating a new variable of the same name:

let a: i32 = 42;
let a: String = &quot;foo&quot;.to_string();

but these are two different variables and the first one still exists after the second one has been created, as evidenced here:

let a: i32 = 42;
let p = &amp;a;
let a: String = &quot;foo&quot;.to_string();
println!(&quot;{}&quot;, a);    // Prints &quot;foo&quot;
println!(&quot;{}&quot;, p);    // Prints &quot;42&quot;

Playground

The original a still exists and can still be accessed indirectly through references even after the second one has been created.

For your use case, if you don't want to use an enum to prevent invalid state transitions, you can use a trait object instead:

use std::fmt::Debug;

trait State: Debug {
    fn process (self: Box&lt;Self&gt;) -&gt; Box&lt;dyn State&gt;;
    fn is_finished (&amp;self) -&gt; bool { false }
}

#[derive (Debug)]
struct Start {}
impl State for Start {
    fn process (self: Box&lt;Self&gt;) -&gt; Box&lt;dyn State&gt; {
        /* Do something then move to the next state */
        Box::new (InProgress {})
    }
}

#[derive (Debug)]
struct InProgress {}
impl State for InProgress {
    fn process (self: Box&lt;Self&gt;) -&gt; Box&lt;dyn State&gt; {
        /* Do something then move to the next state */
        Box::new (Done {})
    }
}

#[derive (Debug)]
struct Done {}
impl State for Done {
    fn process (self: Box&lt;Self&gt;) -&gt; Box&lt;dyn State&gt; {
        self
    }
    fn is_finished (&amp;self) -&gt; bool { true }
}

fn main() {
    let mut s: Box::&lt;dyn State&gt; = Box::new (Start {});
    while !s.is_finished() {
        println!(&quot;Current state: {:?}&quot;, s);
        s = s.process();
        println!(&quot;Moved to state: {:?}&quot;, s);
    }
    println!(&quot;Final state: {:?}&quot;, s);
}

Playground

huangapple
  • 本文由 发表于 2023年8月4日 02:55:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/76830919.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定