3
\$\begingroup\$

Here is a first difference function I threw together for a quick check in another program:

use std::ops::Sub;

fn first_difference<T>(input: &Vec<T>) -> Vec<T::Output>
    where T: Sub<Output = T> + Clone {

    let vec_len = input.len();

    input.iter().take(vec_len-1).enumerate().map(|(ind, val)| {
            val.clone() - input[ind+1].clone()
    }).collect()
}

Some things that I'm not happy with are:

  • Indexing the input while iterating over itself (I could use .chunks(2) but I need to visit each non-first non-last item twice)
  • Cloning each value inside the map (I couldn't make the trait annotation work for &T)

I also don't know if the function needs more machinery to protect against unexpected inputs (I'm not very experienced with strongly typed languages).

What can or should be done with this function?

\$\endgroup\$
2
\$\begingroup\$
  1. Rustfmt is a tool for automatically formatting Rust code to the community-accepted style. Specifically, the { after the where should come on the next line.
  2. Accept a &[T] instead of a &Vec<T>.
use std::ops::Sub;

fn first_difference<T>(input: &[T]) -> Vec<T::Output>
    where T: Sub<Output = T> + Clone
{
    let vec_len = input.len();

    input.iter()
        .take(vec_len - 1)
        .enumerate()
        .map(|(ind, val)| val.clone() - input[ind + 1].clone())
        .collect()
}

fn main() {
    println!("{:?}", first_difference(&[1, 3, 1]));
}

However, this code will panic if presented with an empty input slice because it tries to subtract 1 from 0. This can be addressed by using some iterator adapters:

fn first_difference<T>(input: &[T]) -> Vec<T::Output>
    where T: Sub<Output = T> + Clone
{
    let a = input.iter();
    let b = input.iter().skip(1);

    a.zip(b).map(|(a, b)| a.clone() - b.clone()).collect()
}

The Clone requirement is a little sad to have. You could adjust the bounds to work directly on references. This uses higher-ranked trait bounds (for <'a>):

fn first_difference<T>(input: &[T]) -> Vec<T>
    where for <'a> &'a T: Sub<Output = T>
{
    let a = input.iter();
    let b = input.iter().skip(1);

    a.zip(b).map(|(a, b)| a - b).collect()
}

I think the biggest "weakness" to the function is not one of its own. In Rust, subtraction may panic in certain cases. Specifically, when you exceed the range of the type, the compiler may abort the program. This can be controlled by the programmer, depending on the constraints of the problem at hand .


Why doesn't the compiler complain about that lifetime specifier being undefined? The Rustonomicon says

for<'a> can be read as "for all choices of 'a", and basically produces an infinite list of trait bounds that F must satisfy. Intense.

which I agree is intense, but doesn't seem to tell me what the lifetime is. Is it the lifetime of F, or is it bounded in some other way externally?

for <'a> declares a new generic lifetime, that's why there's no compilation error. The quote from the Nomicon is fairly literal (as far as it can be) — the restriction in the where clause here is "any possible reference to a T needs to be able to be subtracted from another reference to a T. Those two references will share a unified lifetime 'a and the result of the subtraction will produce a T".

It's certainly a mouthful, but it's not impossible to understand once you've seen it a few times.

Specifically, the concrete lifetime will be the same as the incoming slice. This means I was probably being a over-clever, and the code could have been written as:

fn first_difference<'a, T>(input: &'a [T]) -> Vec<T>
    where &'a T: Sub<Output = T>
{
    let a = input.iter();
    let b = input.iter().skip(1);

    a.zip(b).map(|(a, b)| a - b).collect()
}
\$\endgroup\$
  • \$\begingroup\$ Why doesn't the compiler complain about that lifetime specifier being undefined? The Rustonomicon says "for<'a> can be read as "for all choices of 'a", and basically produces an infinite list of trait bounds that F must satisfy. Intense.", which I agree is intense, but doesn't seem to tell me what the lifetime is. Is it the lifetime of F, or is it bounded in some other way externally? \$\endgroup\$ – bright-star Oct 12 '16 at 20:32
  • \$\begingroup\$ @TrevorAlexander updated. \$\endgroup\$ – Shepmaster Oct 12 '16 at 21:56
  • \$\begingroup\$ Odd, the syntax highlighter does not work on your last example. \$\endgroup\$ – Marc-Andre Oct 13 '16 at 14:59
  • \$\begingroup\$ @Marc-Andre hmm; it's passable here. There's a long-standing bug with the syntax highlighting when multiple lifetimes are on one line - it thinks it's a string denoted by ' characters. Is that what you mean? \$\endgroup\$ – Shepmaster Oct 13 '16 at 15:00
  • \$\begingroup\$ @Shepmaster Yes, that was what I was referring to. I'm new to Rust, so I didn't know it was a long time bug. \$\endgroup\$ – Marc-Andre Oct 13 '16 at 15:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.