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

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

问题描述

我有一些类似这样的代码:

I have some code like this:

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?

另一个想法是,如果在不调用该方法的情况下删除了该结构,则实现 Drop panic!.但这并不是很好,因为它是运行时检查,这是非常不可取的.

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_by move_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天全站免登陆