Possible to combine assignment and comparison in an expression?

痞子三分冷 提交于 2019-12-20 03:25:14

问题


In C, it's common to assign and compare in a single expression:

n = n_init;
do {
    func(n);
} while ((n = n.next) != n_init);

As I understand it this can be expressed in Rust as:

n = n_init;
loop {
    func(n);
    n = n.next;
    if n == n_init {
        break;
    }
}

Which works the same as the C version (assuming the body of the loop doesn't use continue).

Is there a more terse way to express this in Rust, or is the example above ideal?

For the purposes of this question, assume ownership or satisfying the borrow checker isn't an issue. It's up to developer to satisfy these requirements.

For example, as an integer:

n = n_init;
loop {
    func(&vec[n]);
    n = vec[n].next;
    if n == n_init {
        break;
    }
}

This may seem obvious that the Rust example is idiomatic Rust - however I'm looking to move quite a lot of this style of loop to Rust, I'm interested to know if there is some better/different way to express it.


回答1:


The idiomatic way to represent iteration in Rust is to use an Iterator. Thus you would implement an iterator that does the n = n.next and then use a for loop to iterate over the iterator.

struct MyIter<'a> {
    pos: &'a MyData,
    start: &'a MyData,
}
impl<'a> Iterator for MyIter<'a> {
    type Item = &'a MyData;
    fn next(&mut self) -> Option<&'a MyData> {
        if self.pos as *const _ == self.start as *const _ {
            None
        } else {
            let pos = self.pos;
            self.pos = self.pos.next;
            Some(pos)
        }
    }
}

it is left as an exercise to the reader to adapt this iterator to be able to start from the first element instead of starting from the second.




回答2:


Rust supports pattern matching in if and while:

  • instead of having a boolean condition, the test is considered successful if the pattern matches
  • as part of pattern matching, you bind the values matched to names

Thus, if instead of having a boolean condition you were building an Option...

fn check(next: *mut Node, init: *mut Node) -> Option<*mut Node>;

let mut n = n_init;
loop {
    func(n);
    if let Some(x) = check(n.next, n_init) {
        n = x;
    } else {
        break;
    }
}

However, if you can use an Iterator instead you'll be much more idiomatic.




回答3:


An assignment in Rust returns the empty tuple. If you are fine with non-idiomatic code you can compare the assignment-result with such an empty tuple and use a logical conjunction to chain your actual loop condition.

let mut current = 3;
let mut parent;

while (parent = get_parent(current)) == () && parent != current {
    println!("currently {}, parent is {}", current, parent);
    current = parent;
}

// example function
fn get_parent(x: usize) -> usize {
    if x > 0 { x - 1 } else { x }
}

// currently 3, parent is 2
// currently 2, parent is 1
// currently 1, parent is 0

This has the disadvantage that entering the loop needs to run logic (which you can avoid with C's do {..} while(); style loops).

You can use this approach inside a do-while macro, but readability isn't that great and at that point a refactoring might be preferable. In any case, this is how it could look:

do_it!({
    println!("{}", n);
} while (n = n + 1) == () && n < 4);

This is the code for the macro:

macro_rules! do_it {
    ($b: block while $e:expr) => {
        loop {
            $b
            if !($e) { break };
        }
    }
}


来源:https://stackoverflow.com/questions/40397672/possible-to-combine-assignment-and-comparison-in-an-expression

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!