I am trying to return reference data from an ephemeral object (data lives longer).
To simplify code, let's say a function builds an object containing a slice. This object will be used, then at some point I'd like to return the reference while the object will be dropped.
The rust compiler complains that the ephemeral object is borrowed and that I can't return data.
The simplified code looks like:
pub struct Bytes<'a>(&'a [u8]);
pub struct Owned<'a>(&'a [u8]);
fn my_function<'a>(input: &'a[u8]) -> Owned<'a> {
let any = Any { data: Bytes(input) };
// this won't work: `any.data` is borrowed
Owned(any.data.as_bytes())
// however, this works
// Owned(any.data.0)
}
Looking at the code above, it seems using any.data.0 is a proper workaround. However, this does not work if there is another intermediate function, for ex:
This typically happens because I'd like to be able to either call the function on an existing Any object (passed by reference), or build an ephemeral one.
Using 'a for both the wrapped reference and the container makes the lifetime shrink to the lifetime of the outer reference to Any. The outer reference is too short-lived as Any is a local variable of your build_owned_consume[2] functions. Use two distinct lifetimes here:
pub struct Bytes<'a>(&'a [u8]);
impl<'a> Bytes<'a> {
pub fn as_bytes<'slf>(&'slf self) -> &'a [u8] {
self.0
}
}
pub struct Any<'a> {
data: Bytes<'a>,
}
struct Owned<'a>(&'a [u8]);
fn return_content<'any, 'content>(any: &'any Any<'content>) -> &'content [u8] {
any.data.as_bytes()
}
fn build_owned<'any, 'content>(any: &'any Any<'content>) -> Owned<'content> {
let s = return_content(any);
Owned(s)
}
fn build_owned_consume<'a>(input: &'a [u8]) -> Owned<'a> {
let any = Any { data: Bytes(input) };
// returning data from object fails:
// error[E0515]: cannot return value referencing local data `any.data`
// --> src/main.rs:32:5
// |
// 37 | Owned(any.data.as_bytes())
// | ^^^^^^--------^^^^^^^^^^^^
// | | |
// | | `any.data` is borrowed here
// | returns a value referencing data owned by the current function
Owned(any.data.as_bytes())
// This works
// Owned(any.data.0)
}
fn build_owned_consume2<'a>(input: &'a [u8]) -> Owned<'a> {
let any = Any { data: Bytes(input) };
// for the same reason, this also fails
// however, here we cannot use `data.0` directly
build_owned(&any)
}
fn main() {}
Thank your for your quick reply!
It seems so obvious with the explanation. That said, I struggled a bit to understand why this does not work in the production code, because I do use different lifetimes.
It seems the problem comes from the .as_bytes() declaration. In my code, I use the AsBytes trait from nom, where all lifetimes are inferred.
When implementing the trait (even with a similar definition), it seems the lifetime is inferred to be the same, causing the problem.
Unfortunately there is no way that you can return a longer lived reference from nom::AsBytes::as_bytes. The only workaround I can think of is returning actually owned data from TryFrom:
That is also my understanding, I'll have a look at nom to see if I can update AsBytes and send a PR. What is not yet clear to me is if such change can cause problems else in existing code.
note: I also tried the standard AsRef trait, and unfortunately it seems to end up with the same problem
In the meantime, I am using a method where the lifetimes work, which allows me to keep parsing zero-copy.
I marked your first reply as solution. Thanks again!
requires that *self remains borrowed so long as the returned borrowed slice is in use, as if the slice of bytes were directly owned by *self (even though this is not the case for your Bytes<'_>). This comes up with a number of std traits too, such as AsRef and Borrow.
Mid-post addendum: As I guess you just discovered, heh.
There's no way to make the trait more flexible that isn't a (SemVer) breaking change AFAIK.