In the spirit of Ref::map
I am trying to define a Rc::map
. Given a Rc<T>
, the map operation would let you produce a RcMap<S>
which contains a clone of the Rc<T>
, guaranteeing that the original T
stays alive as the RcMap<S>
exists, and making it safe for the S
object to refer to the T
object without an explicit lifetime.
The shared-rc
crate and mappable_rc
crates both provide this operation already, but they require the closure passed in to map
to return a reference. I want to be able to map Rc<Vec<i32>>
to RcMap<std::slice::iter<'static, i32>>
. In other words I want to be able to return a type that wraps a reference rather than just direct references. Instead of returning &'a U
I want to return U + 'a
(and then erase the lifetime to be 'static
so that it doesn't infect the RcMap
).
I think I am 95% of the way there. I have a trait ConvertLifetime
that can be used to define conversions via associated type between the 'static
version of a type (e.g. std::slice::Iter<'static, i32>
) and the non-static version (e.g. std::slice::Iter<'a, i32>
). I use this in the signature of the map
operation and rustc seems happy with it.
The issue is when I try to actually use the map operation as defined the error message makes it sound like rustc forgets that myvec
and myvec.iter()
have related lifetimes. If I define a standalone function that's not generic that does the same thing as the closure I pass into map
, I get no error (see this_is_allowed
function below). Why doesn't this compile?
#![allow(dead_code)]
#![allow(unused_variables)]
use std::rc::Rc;
trait ConvertLifetime<'a, 'b> {
type Type;
}
impl<'a, 'b, T: 'a + 'b> ConvertLifetime<'a, 'b> for std::slice::Iter<'a, T> {
type Type = std::slice::Iter<'b, T>;
}
impl<'a, 'b, T: 'a + 'b> ConvertLifetime<'a, 'b> for &'a T {
type Type = &'b T;
}
trait RcMapping {
fn map<'a, U: 'a + ConvertLifetime<'a, 'static>, F: FnOnce(&Self) -> U>(
self: &Rc<Self>,
f: F,
) -> RcMap<Self, U::Type>;
}
/// Taken from the transmute crate. `std::mem::transmute` complains
/// that the source type is not Sized even when it is if the type is generic.
///
/// # Safety
/// See docs for `std::mem::transmute`.
#[inline]
pub unsafe fn transmute<A, B>(a: A) -> B {
let b = ::core::ptr::read(&a as *const A as *const B);
::core::mem::forget(a);
b
}
impl<X> RcMapping for X {
fn map<'a, U: 'a + ConvertLifetime<'a, 'static>, F: FnOnce(&Self) -> U>(
self: &Rc<Self>,
f: F,
) -> RcMap<Self, U::Type> {
RcMap {
parent: self.clone(),
data: unsafe { transmute::<U, U::Type>(f(self)) },
}
}
}
struct RcMap<P: ?Sized, T> {
parent: Rc<P>,
data: T,
}
#[allow(clippy::ptr_arg)]
fn this_is_allowed<'a>(x: &'a Vec<i32>) -> std::slice::Iter<'a, i32> {
x.iter()
}
fn why_is_this_not() {
let x = Rc::new(vec![1, 2, 3]);
// AFAICT the map closure is doing the same thing as
// `this_is_allowed`. rustc seems to think that the lifetime of
// `x` and `x.iter()` are unrelated?
let y = x.map(|x| x.iter());
}
Produces the error:
--> src/lib.rs:64:23
|
64 | let y = x.map(|x| x.iter());
| -- ^^^^^^^^ returning this value requires that `'1` must outlive `'2`
| ||
| |return type of closure is std::slice::Iter<'2, i32>
| has type `&'1 std::vec::Vec<i32>`