HiveBrain v1.2.0
Get Started
← Back to all entries
patternrustMinor

Robustness of this first difference function

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
thisfunctiondifferencerobustnessfirst

Problem

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

use std::ops::Sub;

fn first_difference(input: &Vec) -> Vec
    where T: Sub + 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?

Solution


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



  • Accept a &[T] instead of a &Vec.



use std::ops::Sub;

fn first_difference(input: &[T]) -> Vec
    where T: Sub + 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(input: &[T]) -> Vec
    where T: Sub + 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 ):

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

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

Code Snippets

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]));
}
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()
}
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()
}
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()
}

Context

StackExchange Code Review Q#144033, answer score: 2

Revisions (0)

No revisions yet.