Sun*_*rma 8 api-design type-safety rust
我有一些像这样的代码:
foo.move_right_by(10);
//do some stuff
foo.move_left_by(10);
Run Code Online (Sandbox Code Playgroud)
最终我执行这两个操作非常重要,但我经常忘记在第一个之后执行第二个操作.它会导致很多错误,我想知道是否有一种习惯性的Rust方法来避免这个问题.当我忘记时,有没有办法让Rust编译器让我知道?
我的想法可能是某种类似的东西:
// 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();
Run Code Online (Sandbox Code Playgroud)
有没有更好的方法来实现这一目标?
另一个想法是实现Drop
并且panic!
如果在没有调用该方法的情况下删除结构.这不是很好,因为它是运行时检查,这是非常不受欢迎的.
编辑:我意识到我的例子可能太简单了.所涉及的逻辑可能变得非常复杂.例如,我们有这样的事情:
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();
Run Code Online (Sandbox Code Playgroud)
请注意如何以良好的,正确嵌套的顺序执行操作.唯一重要的是事后总是调用逆操作.有时需要以某种方式指定顺序,以使代码按预期工作.
我们甚至可以这样:
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...
Run Code Online (Sandbox Code Playgroud)
Pet*_*all 14
您可以使用幻像类型来携带附加信息,这些信息可用于类型检查而无需任何运行时成本.一个限制是move_left_by
和move_right_by
,因为他们需要改变的类型必须返回一个新的拥有对象,但通常这不会是一个问题.
此外,如果您实际上没有在结构中使用类型,编译器会抱怨,因此您必须添加使用它们的字段.Rust std
提供零大小的PhantomData
类型作为此目的的便利.
您的约束可以像这样编码:
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)
}
}
Run Code Online (Sandbox Code Playgroud)
你可以像这样使用它:
// 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);
}
Run Code Online (Sandbox Code Playgroud)
如果您错过了所需的方法之一,
fn main(){
let thing = Thing::new()
.move_right_by(3);
do_something(&thing);
}
Run Code Online (Sandbox Code Playgroud)
然后你会得到一个编译错误:
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>`
Run Code Online (Sandbox Code Playgroud)
#[must_use]
在这种情况下,我不认为你真正想要的是什么.这是解决问题的两种不同方法.第一个是在封闭中包含你需要做的事情,并抽象掉直接调用:
#[derive(Debug)]
pub struct Foo {
x: isize,
y: isize,
}
impl Foo {
pub fn new(x: isize, y: isize) -> Foo {
Foo { x: x, y: y }
}
fn move_left_by(&mut self, steps: isize) {
self.x -= steps;
}
fn move_right_by(&mut self, steps: isize) {
self.x += steps;
}
pub fn do_while_right<F>(&mut self, steps: isize, f: F)
where F: FnOnce(&mut Self)
{
self.move_right_by(steps);
f(self);
self.move_left_by(steps);
}
}
fn main() {
let mut x = Foo::new(0, 0);
println!("{:?}", x);
x.do_while_right(10, |foo| {
println!("{:?}", foo);
});
println!("{:?}", x);
}
Run Code Online (Sandbox Code Playgroud)
第二种方法是创建一个包装类型,它在删除时调用该函数(类似于Mutex::lock
生成一个MutexGuard
解锁Mutex
时删除的函数):
#[derive(Debug)]
pub struct Foo {
x: isize,
y: isize,
}
impl Foo {
fn new(x: isize, y: isize) -> Foo {
Foo { x: x, y: y }
}
fn move_left_by(&mut self, steps: isize) {
self.x -= steps;
}
fn move_right_by(&mut self, steps: isize) {
self.x += steps;
}
pub fn returning_move_right(&mut self, x: isize) -> MovedFoo {
self.move_right_by(x);
MovedFoo {
inner: self,
move_x: x,
move_y: 0,
}
}
}
#[derive(Debug)]
pub struct MovedFoo<'a> {
inner: &'a mut Foo,
move_x: isize,
move_y: isize,
}
impl<'a> Drop for MovedFoo<'a> {
fn drop(&mut self) {
self.inner.move_left_by(self.move_x);
}
}
fn main() {
let mut x = Foo::new(0, 0);
println!("{:?}", x);
{
let wrapped = x.returning_move_right(5);
println!("{:?}", wrapped);
}
println!("{:?}", x);
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1424 次 |
最近记录: |