Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I specify the lifetime of an AsRef?

Tags:

rust

I'm trying to write a function that joins two iterables whose items can be converted into OsStr references and have had a tremendous amount of difficulty trying to specify the reference's lifetime.

use std::convert::AsRef;
use std::ffi::OsStr;
use std::marker::PhantomData;

#[derive(Clone, Debug)]
#[must_use = "join_args is lazy and does nothing unless consumed"]
pub struct JoinArgs<'a, A: 'a, B: 'a> {
    a: A,
    b: B,
    state: JoinState,

    phantomA: PhantomData<&'a A>,
    phantomB: PhantomData<&'a B>,
}

#[derive(Clone, Debug)]
enum JoinState {
    Both,
    Front,
    Back,
}

/// Chains two iterable argument lists.
pub fn join_args<'a, I1, S1, I2, S2>(iter1: I1, iter2: I2) -> JoinArgs<'a, I1::IntoIter, I2::IntoIter>
where
    I1: IntoIterator<Item = S1>,
    S1: AsRef<OsStr> + 'a,
    I2: IntoIterator<Item = S2>,
    S2: AsRef<OsStr> + 'a
{
    let a = iter1.into_iter();
    let b = iter2.into_iter();
    JoinArgs{a, b, state: JoinState::Both, phantomA: PhantomData, phantomB: PhantomData}
}

impl<'a, A, SA, B, SB> Iterator for JoinArgs<'a, A, B>
where
    A: Iterator<Item = SA>,
    SA: AsRef<OsStr> + 'a,
    B: Iterator<Item = SB>,
    SB: AsRef<OsStr> + 'a
{
    type Item = &'a OsStr;

    fn next(&mut self) -> Option<Self::Item> {
        // All throughout here, I'm getting E0597 errors.
        match self.state {
            JoinState::Both => match self.a.next() {
                Some(x) => Some(x.as_ref()),
                None => {
                    self.state = JoinState::Back;
                    self.b.next().map(|x| x.as_ref())
                }
            },
            JoinState::Front => self.a.next().map(|x| x.as_ref()),
            JoinState::Back => self.b.next().map(|x| x.as_ref()),
        }
    }
}

I'm trying to clean up a bunch of code I have where I'm using map and chain to coerce the types myself (like in the test below). If there's a better way to do this, I'm all ears. :)

#[cfg(test)]
mod tests {
    use super::*;

    use std::ffi::OsString;

    #[test]
    fn test_join_args() {
        let a = &[OsStr::new("abc"), OsStr::new("def")];
        let b = vec![OsString::from("ghi")];
        let result: Vec<&OsStr> = join_args(a, &b).collect();
        assert_eq!(result, [
                   OsStr::new("abc"),
                   OsStr::new("def"),
                   OsStr::new("ghi"),
        ]);
    }
}

(This is on Rust stable, version 1.23.0)

like image 990
Ross Light Avatar asked Feb 11 '18 17:02

Ross Light


1 Answers

You don't.

AsRef is a trait and its definition is fixed:

pub trait AsRef<T>
where
    T: ?Sized, 
{
    fn as_ref(&self) -> &T;
}

It can only be used to take a reference to one thing and get another reference with the same lifetime.

Your code would allow an Iterator<Item = OsString>:

use std::ffi::{OsStr, OsString};

fn proof<'a, I>(_: I)
where
    I: Iterator,
    I::Item: AsRef<OsStr> + 'a,
{}

fn main() {
    proof(vec![OsString::new()].into_iter());
}

If you then called AsRef on the item, you'd have a reference to something that doesn't live beyond the function. However, you are attempting to return that reference, which would be invalid. Thus, Rust has prevented you from introducing memory unsafety; hooray!

This is the exact same problem as How to use the lifetime on AsRef


The good news is that you can express what you want, you just need to state that your iterator returns references:

impl<'a, A, B, S1, S2> Iterator for JoinArgs<'a, A, B>
where
    A: Iterator<Item = &'a S1>,
    S1: AsRef<OsStr> + 'a,
    B: Iterator<Item = &'a S2>,
    S2: AsRef<OsStr> + 'a,
{
    // ...
}

As an aside, you don't need to have the PhantomData or the lifetime on your struct:

use std::convert::AsRef;
use std::ffi::OsStr;

#[derive(Clone, Debug)]
#[must_use = "join_args is lazy and does nothing unless consumed"]
pub struct JoinArgs<A, B> {
    a: A,
    b: B,
    state: JoinState,
}

#[derive(Clone, Debug)]
enum JoinState {
    Both,
    Front,
    Back,
}

/// Chains two iterable argument lists.
pub fn join_args<I1, I2>(iter1: I1, iter2: I2) -> JoinArgs<I1::IntoIter, I2::IntoIter>
where
    I1: IntoIterator,
    I2: IntoIterator,
{
    JoinArgs {
        a: iter1.into_iter(),
        b: iter2.into_iter(),
        state: JoinState::Both,
    }
}

impl<'a, A, B, S1, S2> Iterator for JoinArgs<A, B>
where
    A: Iterator<Item = &'a S1>,
    S1: AsRef<OsStr> + 'a,
    B: Iterator<Item = &'a S2>,
    S2: AsRef<OsStr> + 'a,
{
    type Item = &'a OsStr;

    fn next(&mut self) -> Option<Self::Item> {
        match self.state {
            JoinState::Both => match self.a.next() {
                Some(x) => Some(x.as_ref()),
                None => {
                    self.state = JoinState::Back;
                    self.b.next().map(AsRef::as_ref)
                }
            },
            JoinState::Front => self.a.next().map(AsRef::as_ref),
            JoinState::Back => self.b.next().map(AsRef::as_ref),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use std::ffi::OsString;

    #[test]
    fn test_join_args() {
        let a = &[OsStr::new("abc"), OsStr::new("def")];
        let b = vec![OsString::from("ghi")];
        let result: Vec<&OsStr> = join_args(a, &b).collect();
        assert_eq!(
            result,
            [OsStr::new("abc"), OsStr::new("def"), OsStr::new("ghi"),]
        );
    }
}

See also:

  • How to use the lifetime on AsRef
like image 163
Shepmaster Avatar answered Oct 21 '22 19:10

Shepmaster