[Solved] Function taking slice of objects as well as slice of references to objects

Hello everybody,

after applying a few combinators to a slice of objects &[T] I end up with a slice of references &[&T] which I like to process in another function. So the function parameter must be of type &[&T]. In order to reuse this function, I have to convert slice of objects to slices of references which is cumbersome and feels wrong. I'm wondering, if I'm missing something here which would allow me to resolve this in a more elegant way.

Here is a simplified example -- cf. Playground.

struct Age {
   pub age: u32,
}

fn process_ages(ages: &[Option<Age>]) -> u32 {
    let somes: Vec<&Age> = ages.iter()
        .filter(|x| x.is_some())
        .map(|x| x.as_ref().unwrap())
        .collect();

    sum_ages(&somes)
}

fn sum_ages(ages: &[&Age]) -> u32 {
    ages.iter().fold(0, |acc, e| acc + e.age)
}

fn main() {
    let ages: Vec<Option<Age>> = vec![Some(Age { age: 1 }), None, Some(Age { age: 2 })];

    assert_eq!(process_ages(&ages), 3);
}

In this example, the total sum of all ages from a list of Option<Age>s should be computed. The function of interest is sum_ages that takes a slice of type &[&Age] instead a of slice of &[Age] which is more general and more applicable.

Do you have any idea how to ease the pain and make sum_ages reusable without requiring a transformation of &[&Age] to &[&Age] all the time?

Thanks in advance!

You can avoid getting references by using ages.into_iter() or ages.iter().cloned() (they iterate over T instead of &T).

As for fn sum_ages, you could make it take an iterator: Rust Playground

If you want to skip the cloning, you could use the AsRef trait (playground).

impl AsRef<Age> for Age {
    fn as_ref(&self) -> &Age {
        self
    }
}

fn sum_ages_2<A: AsRef<Age>>(ages: &[A]) -> u32 {
    ages.iter().fold(0, |acc, e| acc + e.as_ref().age)
}

fn process_ages(ages: &[Option<Age>]) -> u32 {
    let somes: Vec<&Age> = ages.iter()
        .filter(|x| x.is_some())
        .map(|x| x.as_ref().unwrap())
        .collect();

    sum_ages_2(&somes)
}

fn main() {
    let ages: Vec<Option<Age>> = vec![Some(Age { age: 1 }), None, Some(Age { age: 2 })];

    assert_eq!(process_ages(&ages), 3);

    let moar_ages = vec![Age { age: 1 }, Age { age: 2 }];
    assert_eq!(sum_ages_2(&moar_ages), 3);
}

This works because you are effectively "abstracting" over whether you have something or a reference to it. In this case, as long as you can get a reference to the thing then that's all we care about.

Thank you guys for your comments.

I was already pondering about using AsRef but it strikes me as odd that I need to set a property of the object in order to ease on how I pass it to functions. I will follow this path nevertheless.

I have a question about your vector. You are storing Option in the vector. Is this something that's okay to do? It seems like you'd have to go through extra work to make sure that option contains something later on, rather than just storing Age in the vector.

I only ask because I'm still learning Rust.

Hello @jeramyRR,

yes, that's perfectly fine to do.

Option and its sibling Result are types that encapsulate the result of a computation that might have not produced a valid result or even failed doing so, respectively. So if you apply such a computation to a set of input objects you end up with a list of Options or Results that are used in a following step of your algorithm.

In my actual case, I get a vector of Results from the first step of my algorithm. The second step of my algorithm is only interested in successful results of step 1. So I need to filter / partition the results before I hand them over to the second step. But it has been easier to make up a minimized example using Option.

I hope this helps,
Lukas

what is the most idiomatic way here to implements AsRef and use it or to use Borrow with has a blanko impl for Borrow<T> for T?

I believe AsRef and Borrow meant for two slightly different things. AsRef abstracts over whether you have a reference or own the resource, whereas Borrow abstracts over whether you have a mutable or immutable reference.

I believe The Book has a chapter explaining more.

from the book:

The Borrow trait is used when you’re writing a data structure, and you want to use either an owned or borrowed type as synonymous for some purpose.

sounds to me like that Borrow is the preferred way here.

1 Like