Search code examples
rustdigits

How do I split an integer into individual digits?


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?


Solution

  • 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: