Hey all,
for a library I'm working on, I want to convert an owned value containing potentially multiple levels of other owned values into its "deep" borrowed representation.
For example:
// From -> Into
Vec<i32> -> &[i32]
Vec<Vec<i32>> -> &[&[i32]]
for<T: DeepBorrow> Vec<T> -> &[T::DeeplyBorrowed]
the first problem that I encountered, is that it seems fundamentally impossible to have the following:
let borrowed: &[&[i32]] = vec![vec![1,2,3]];
because, we need to perform an intermediate step of converting Vec<Vec<i32>>
into Vec<&[i32]>
and can't just return a reference to this intermediate value.
So what I have so far is DeepBorrow
trait which allows me borrow the innermost owned value. What I tried to do, was to express the following code as a trait:
let inp = vec![vec![vec![1, 2, 3], vec![4, 5, 6]]];
let i3: Vec<Vec<&[i32]>> = inp
.iter()
.map(|v| v.iter().map(|v| &v[..]).collect())
.collect();
let i2: Vec<&[&[i32]]> = i3.iter().map(|v| &v[..]).collect();
let i1: &[&[&[i32]]] = &i2[..];
My solution looks like this:
trait DeepBorrow
{
type Borrowed<'b>
where
Self: 'b;
fn deep_borrow(&self) -> Self::Borrowed<'_>;
}
impl<I: DeepBorrow> DeepBorrow for Vec<I> {
type Borrowed<'b> = Vec<I::Borrowed<'b>> where Self: 'b;
fn deep_borrow(&self) -> Self::Borrowed<'_> {
self.iter().map(|i| i.deep_borrow()).collect()
}
}
impl<T> DeepBorrow for Vec<&[T]>
{
type Borrowed<'b> = &'b [&'b [T]] where Self: 'b;
fn deep_borrow(&self) -> Self::Borrowed<'_> {
&self[..]
}
}
impl DeepBorrow for Vec<i32> {
type Borrowed<'b> = &'b [i32] where Self: 'b;
fn deep_borrow(&self) -> Self::Borrowed<'_> {
&self[..]
}
}
[Playground]
Calling deep_borrow
recursively on the input from before gives the expected slice of slice of slices (see the playground). The idea of DeepBorrow
is that the impls for Vec<i32>
and Vec<&[T]>
serve as the base case of the recursion.
This is cool, but not quite yet what I need. I feel like it should be possible to have a trait with a fn with_deep_borrow<F>(&self, f: F) where F: FnOnce(DeepBorrow)
which encapsulates the recursive calling of deep_borrow
and passes the deeply borrowed value to a closure. This should work, as all the intermediate values are on the stack.
My most promising attempt thus far looks like this:
/// Foo should really be WithDeepBorrow
trait Foo<B: ?Sized> {
/// and this with_deep_borrow
fn foo<F>(&self, f: F)
where
F: Fn(&B);
}
impl<'a, I> Foo<[&'a [i32]]> for Vec<I>
where
Self: DeepBorrow,
<Self as DeepBorrow>::Borrowed<'a>: Foo<[&'a [i32]]>,
{
fn foo<F>(&self, f: F)
where
F: Fn(&[&'a [i32]]),
{
let d = self.deep_borrow();
d.foo(f);
}
}
impl<'a> Foo<[&'a [i32]]> for Vec<&'a [i32]>
{
fn foo<F>(&self, f: F)
where
F: FnOnce(&[&'a [i32]]),
{
f(&self[..])
}
}
[Playground]
but I just can't it to work. I've tried many variations of the above, adding a lifetime parameter to Foo, using HRTB in some places and other stuff, but either I end up with conflicting implementations or or some other type errors.
I hope someone is able to help me with this. Using GATs is not a problem as they are soon to be stable, but I'd like to refrain from using other unstable features.
Context: Why do I think I need this?
In the library I'm currently writing, I have a proc macro which can be used to annotate functions and serves as kind of a cache plus extra stuff. For example
#[sub_circuit]
fn circ(input: &[Share]) { ... }
gets transformed into roughly
fn circ(input: &[Share]) {
fn inner(input: &[Share]) { todo!("Original function body") }
static CACHE: Cache = Cache // Caches the circuit computed by inner for a
//specific input length
// when the circuit computed by inner is not in the Cache
// create Vec<Share> which represent the input shares to the circuit
let circ_inputs: Vec<Share> = create_inputs(input);
inner(&circ_inputs[..]);
// store the circuit created by inner() into Cache
}
But I'd like to support different, potentially nested input types, e.g. &[&[Share]]
. I think I'm able to do create_inputs
but it needs to create new Share
s and therefore an owned version of the original input. But then I need to call inner()
with the borrowed version of the newly created nestes input shares. This is what I'm struggling with.
I feel like I might have fallen into the trap of trying to have a too generic API and will likely not use this approach even if I find a solution to WithDeepBorrow
(which actually doesn't quite express what I want, since the user should be able to use both Vec<[Share; N]>
or &[&[Share; N]]
as input types, which whould not be possible with current design. But it still bugs me that I think the above outlined with_deep_borrow
should be possible but I can't get it to work.