使用 Rust 编译器防止忘记调用方法

编程入门 行业动态 更新时间:2024-10-07 04:33:16
本文介绍了使用 Rust 编译器防止忘记调用方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

限时送ChatGPT账号..

我有一些这样的代码:

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屋!

更多推荐

[db:关键词]

本文发布于:2023-04-18 03:25:48,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/928312.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:编译器   方法   Rust

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!