使用 Rust 编译器防止忘记调用方法 [英] Using the Rust compiler to prevent forgetting to call a method

查看:22
本文介绍了使用 Rust 编译器防止忘记调用方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一些这样的代码:

foo.move_right_by(10);
//do some stuff
foo.move_left_by(10);

最终执行这两个操作真的很重要,但我经常忘记在第一个之后执行第二个.它会导致很多错误,我想知道是否有一种惯用的 Rust 方法来避免这个问题.有没有办法让 Rust 编译器在我忘记时让我知道?

It's really important that I perform both of those operations eventually, but I often forget to do the second one after the first. It causes a lot of bugs and I'm wondering if there is an idiomatic Rust way to avoid this problem. Is there a way to get the rust compiler to let me know when I forget?

我的想法是可能以某种方式拥有这样的东西:

My idea was to maybe somehow have something like this:

// must_use will prevent us from forgetting this if it is returned by a function
#[must_use]
pub struct MustGoLeft {
    steps: usize;
}

impl MustGoLeft {
    fn move(&self, foo: &mut Foo) {
        foo.move_left_by(self.steps);
    }
}

// If we don't use left, we'll get a warning about an unused variable
let left = foo.move_left_by(10);

// Downside: move() can be called multiple times which is still a bug
// Downside: left is still available after this call, it would be nice if it could be dropped when move is called
left.move();

有没有更好的方法来实现这一点?

Is there a better way to accomplish this?

另一个想法是实现 Droppanic! 如果结构体在没有调用该方法的情况下被删除.但这不是很好,因为它是运行时检查,这是非常不受欢迎的.

Another idea is to implement Drop and panic! if the struct is dropped without having called that method. This isn't as good though because it's a runtime check and that is highly undesirable.

我意识到我的例子可能太简单了.所涉及的逻辑可能会变得非常复杂.例如,我们有这样的事情:

I realized my example may have been too simple. The logic involved can get quite complex. For example, we have something like this:

foo.move_right_by(10);
foo.open_box(); // like a cardboard box, nothing to do with Box<T>
foo.move_left_by(10);
// do more stuff...
foo.close_box();

请注意操作不是以良好的、正确嵌套的顺序执行的.唯一重要的是逆运算总是在之后调用.为了使代码按预期工作,有时需要以某种方式指定顺序.

Notice how the operations aren't performed in a nice, properly nested order. The only thing that's important is that the inverse operation is always called afterwards. The order sometimes needs to be specified in a certain way in order to make the code work as expected.

我们甚至可以有这样的东西:

We can even have something like this:

foo.move_right_by(10);
foo.open_box(); // like a cardboard box, nothing to do with Box<T>
foo.move_left_by(10);
// do more stuff...
foo.move_right_by(10);
foo.close_box();
foo.move_left_by(10);
// do more stuff...

推荐答案

您可以使用幻像类型来携带附加信息,这些信息可用于类型检查而无需任何运行时成本.一个限制是 move_left_bymove_right_by 必须返回一个新拥有的对象,因为它们需要更改类型,但这通常不会成为问题.

You can use phantom types to carry around additional information, which can be used for type checking without any runtime cost. A limitation is that move_left_by and move_right_by must return a new owned object because they need to change the type, but often this won't be a problem.

此外,如果您实际上没有在结构中使用类型,编译器会抱怨,因此您必须添加使用它们的字段.Rust 的 std 提供了零大小的 PhantomData 类型,以便为此目的提供便利.

Additionally, the compiler will complain if you don't actually use the types in your struct, so you have to add fields that use them. Rust's std provides the zero-sized PhantomData type as a convenience for this purpose.

您的约束可以这样编码:

Your constraint could be encoded like this:

use std::marker::PhantomData;

pub struct GoneLeft;
pub struct GoneRight;
pub type Completed = (GoneLeft, GoneRight);

pub struct Thing<S = ((), ())> {
    pub position: i32,
    phantom: PhantomData<S>,
}


// private to control how Thing can be constructed
fn new_thing<S>(position: i32) -> Thing<S> {
    Thing {
        position: position,
        phantom: PhantomData,
    }
}

impl Thing {
    pub fn new() -> Thing {
        new_thing(0)
    }
}

impl<L, R> Thing<(L, R)> {
    pub fn move_left_by(self, by: i32) -> Thing<(GoneLeft, R)> {
        new_thing(self.position - by)
    }

    pub fn move_right_by(self, by: i32) -> Thing<(L, GoneRight)> {
        new_thing(self.position + by)
    }
}

你可以这样使用它:

// This function can only be called if both move_right_by and move_left_by
// have been called on Thing already
fn do_something(thing: &Thing<Completed>) {
    println!("It's gone both ways: {:?}", thing.position);
}

fn main() {
    let thing = Thing::new()
          .move_right_by(4)
          .move_left_by(1);
    do_something(&thing);
}

如果您错过了所需的方法之一,

And if you miss one of the required methods,

fn main(){
    let thing = Thing::new()
          .move_right_by(3);
    do_something(&thing);
}

然后你会得到一个编译错误:

then you'll get a compile error:

error[E0308]: mismatched types
  --> <anon>:49:18
   |
49 |     do_something(&thing);
   |                  ^^^^^^ expected struct `GoneLeft`, found ()
   |
   = note: expected type `&Thing<GoneLeft, GoneRight>`
   = note:    found type `&Thing<(), GoneRight>`

这篇关于使用 Rust 编译器防止忘记调用方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆