Simplify a return + inc

There's nothing tricky / magical in this code:

    fn next_id(&mut self) -> u64 {
        let nfi = self.next_free_id;
        self.next_free_id = nfi + 1;
        nfi
    }

It just seems slightly verbose. Is there a way to go from 3 lines to 2 (or maybe 1) ?

2 Likes

Here's a two-line version, though I'm not sure it's an improvement:

    fn next_id(&mut self) -> u64 {
        let next = self.next_free_id + 1;
        std::mem::replace(&mut self.next_free_id, next)
    }

Another questionable version:

    fn next_id(&mut self) -> u64 {
        self.next_free_id += 1;
        self.next_free_id - 1
    }

If you store the previous ID instead of the next ID, that one simplifies to:

    fn next_id(&mut self) -> u64 {
        self.prev_id += 1;
        self.prev_id
    }
3 Likes

C has something like n ++ which (1) returns the old value of n and (2) also increments n.

Is there nothing like this in Rust ?

1 Like

There is not.

5 Likes

Please take a look at the following thread a year or so ago which discusses why/why not this is in rust, and the pros/cons about [post|pre][in|de]crement operators.

3 Likes

I've abused tuples for this before, but it looks even worse with long identifiers:

(self.next_free_id, self.next_free_id += 1).0
12 Likes

With a Pony-like "replace" assignment operator (= in Pony, write it as <- here) that returns the old value from an assignment, it could be:

    fn next_id(&mut self) -> u64 {
        self.next_free_id <- self.next_free_id + 1
    }
2 Likes

How about mem::replace?

fn next_id(&mut self) -> u64 {
    let next_id = self.next_free_id + 1;
    mem::replace(&mut self.next_free_id, next_id)
}
2 Likes

I’ve wanted this in Rust for a long time.

1 Like

Here's the solution I ended up going with:

use super::*;

pub trait U64UtilT {
    fn n_pp(&mut self) -> u64;
    fn pp_n(&mut self) -> u64;
}

impl U64UtilT for u64 {
    fn n_pp(&mut self) -> u64 {
        let old = *self;
        *self = *self + 1;
        return old;
    }

    fn pp_n(&mut self) -> u64 {
        *self = *self + 1;
        return *self;
    }
}

@alice: Isn't this missing a return ?

@cuviper: I'm not sure if this is interesting or syntax abusive. I'm not familiar with Rust evaluation order. Does it guarantee that tuples are evaluated left to right?

AFAIK it's not strictly specified, but "the ship has sailed", as Niko put it in this thread:

2 Likes

How about one line?

struct Struct {
    unused_ids: std::ops::RangeFrom<u64>,
}

impl Struct {
    fn next_unused_id(&mut self) -> u64 {
        self.unused_ids.next().unwrap()
    }
}
27 Likes

It's probably the extra ; at the end that @alice added by accident. The return keyword is typically used for early returns in rust - something like

fn foo(...) -> Result<(),()> {
   if x == 5 {
      return Err(()) 
   }
   do_stuff();
   do_more_stuff();
   do_other_stuff();
   Ok(())
}
1 Like

@eugene2k: Interesting, I didn't realize mem::replace returned a T

That's, like, it's whole thing! Otherwise it would be the same as an ordinary assignment.

2 Likes

For everyone who wonders if that's a good approach. Yes it fucking is! This boils down to three instructions:

example::Struct::next_unused_id:
        movq    (%rdi), %rax
        leaq    1(%rax), %rcx
        movq    %rcx, (%rdi)
        retq

no jump, no panic, just what you would expect and what a hand written + 1 would do. Rust is so amazing when it comes to zero cost abstractions.

18 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.