17

I'm writing a function that requires the individual digits of a larger integer to perform operations on.

I've tried the following:

fn example(num: i32) {
    // I can safely unwrap because I know the chars of the string are going to be valid
    let digits = num.to_string().chars().map(|d| d.to_digit(10).unwrap());
    for digit in digits {
        println!("{}", digit)
    }
}

But the borrow checker says the string doesn't live long enough:

error[E0716]: temporary value dropped while borrowed
 --> src/lib.rs:3:18
  |
3 |     let digits = num.to_string().chars().map(|d| d.to_digit(10).unwrap());
  |                  ^^^^^^^^^^^^^^^                                         - temporary value is freed at the end of this statement
  |                  |
  |                  creates a temporary which is freed while still in use
4 |     for digit in digits {
  |                  ------ borrow later used here
  |
  = note: consider using a `let` binding to create a longer lived value

The following does work:

let temp = num.to_string();
let digits = temp.chars().map(|d| d.to_digit(10).unwrap());

But that looks even more contrived.

Is there a better, and possibly more natural way of doing this?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Electric Coffee
  • 11,733
  • 9
  • 70
  • 131

1 Answers1

28

But the borrow checker says the string doesn't live long enough.

That's because it doesn't. You aren't using the iterator, so the type of digits is

std::iter::Map<std::str::Chars<'_>, <closure>>

That is, a yet-to-be-evaluated iterator that contains references to the allocated string (the unnamed lifetime '_ in Chars). However, since that string has no owner, it is dropped at the end of the statement; before the iterator is consumed.

So, yay for Rust, it prevented a use-after-free bug!

Consuming the iterator would "solve" the problem, as the references to the allocated string would not attempt to live longer than the allocated string; they all end at the end of the statement:

let digits: Vec<_> = num.to_string().chars().map(|d| d.to_digit(10).unwrap()).collect();

If you wanted to return an iterator, you can then convert the Vec back into an iterator:

fn digits(num: usize) -> impl Iterator<Item = u32> {
    num.to_string()
        .chars()
        .map(|d| d.to_digit(10).unwrap())
        .collect::<Vec<_>>()
        .into_iter()
}

As for an alternate solution, there's the math way, stolen from the C++ question to create a vector:

fn x(n: usize) -> Vec<usize> {
    fn x_inner(n: usize, xs: &mut Vec<usize>) {
        if n >= 10 {
            x_inner(n / 10, xs);
        }
        xs.push(n % 10);
    }
    let mut xs = Vec::new();
    x_inner(n, &mut xs);
    xs
}

fn main() {
    let num = 42;
    let digits: Vec<_> = num.to_string().chars().map(|d| d.to_digit(10).unwrap()).collect();
    println!("{:?}", digits);
    let digits = x(42);
    println!("{:?}", digits);
}

However, you might want to add all the special case logic for negative numbers, and testing wouldn't be a bad idea.

You might also want a fancy-pants iterator version:

fn digits(mut num: usize) -> impl Iterator<Item = usize> {
    let mut divisor = 1;
    while num >= divisor * 10 {
        divisor *= 10;
    }

    std::iter::from_fn(move || {
        if divisor == 0 {
            None
        } else {
            let v = num / divisor;
            num %= divisor;
            divisor /= 10;
            Some(v)
        }
    })
}

Or the completely custom type:

struct Digits {
    n: usize,
    divisor: usize,
}

impl Digits {
    fn new(n: usize) -> Self {
        let mut divisor = 1;
        while n >= divisor * 10 {
            divisor *= 10;
        }

        Digits {
            n: n,
            divisor: divisor,
        }
    }
}

impl Iterator for Digits {
    type Item = usize;

    fn next(&mut self) -> Option<Self::Item> {
        if self.divisor == 0 {
            None
        } else {
            let v = Some(self.n / self.divisor);
            self.n %= self.divisor;
            self.divisor /= 10;
            v
        }
    }
}

fn main() {
    let digits: Vec<_> = Digits::new(42).collect();
    println!("{:?}", digits);
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • I don't see how that would become a use after free bug. Wouldn't it just get used immediately, and THEN freed afterwards? – Electric Coffee Jan 08 '17 at 18:55
  • about adding special case testing for negative numbers: the function that uses this uses `u32`, negative numbers will never happen – Electric Coffee Jan 08 '17 at 19:01
  • 3
    @ElectricCoffee: The use-after-free comes because `to_string` creates a temporary which only lives as long as the statement in which it was created. However your `digits` iterator, which works by referencing this temporary, lives far longer, which is incorrect. If you call `collect` immediately in the same statement, then there is no longer a problem. – Matthieu M. Jan 08 '17 at 19:03
  • Beat me to it @MatthieuM.! – Shepmaster Jan 08 '17 at 19:04
  • @ElectricCoffee the question mentions nothing about the range of the integer, so I felt free to restrict my answer to unsigned values, but felt it prudent to warn future readers. – Shepmaster Jan 08 '17 at 19:06
  • @Shepmaster very valid argument – Electric Coffee Jan 08 '17 at 19:08
  • 1
    If performance is a concern it could be further optimized by using /100 instead of /10 and using a lookup table for the 100 possibilities, yielding 2 digits at a time. I assume that the compiler can fuse % and / into a single instruction. – the8472 Jan 08 '17 at 23:02
  • @the8472 yeah, I'm a little surprised that `divmod` isn't exposed yet, IIRC LLVM has it. I also hope that is an automatic optimization. – Shepmaster Jan 08 '17 at 23:03