Specifying a common lifetime for returned tuple of dependent variables inside unnamed scope

Hello,

I'm pretty new to Rust - apologies if this is obvious.

There's a lot of documentation on how to specify lifetime for functions/methods, but I'm confused about how to apply it on single variables in the following case.

Consider the code in Rust Playground, which fails to compile with s does not live long enough. How would you add lifetime specifiers to hint to the compiler that the returned tuple and both variables should share the same lifetime?

Note that this is a minimal example that reproduces something else I was trying to do. The context for this is I was trying to use tracing spans inside an unnamed block for profiling a section of my code, but still wanted the variables I was generating out of the block, and ran into this issue. Is there maybe a better way to handle this pattern?

You can't. What happens at the end of the scope is that s is moved:

After moving resources, the previous owner can no longer be used. This avoids creating dangling pointers.

o points to the old location of s, the location before the move. But the value there is no longer valid and can't be referenced.

1 Like

Aah, thanks, that's why it didn't click with me! Just to be sure I understand well s is moved just by constructing the tuple and then that tuple is moved out of the scope, am I correct?

I imagine I could create a struct containing both so that they cannot be moved/referenced independently, but this seems like a lof of boilerplate for just a simple profiling block ; this might also reoccur in a different form elsewhere in the code, so I would need one struct per case. I can probably create/exit the tracing::span in another way in my code, but the block scope seemed like a simple enough solution.

Do you have other suggestions for what I'm trying to do?

Yes.

This sounds like you want to create a self-referential struct, which suffers from the same problem.

I'm not exactly sure how your problem applies to a tracing span, would you mind expanding your example a bit? If you need to do is enter a span without opening another scope, you can use Span::enter. It returns a RAII resource that leaves the span again when it is dropped.

1 Like

Whoops, guilty as charged! :grimacing:

My code looks like this before adding the span:

fn main() {
  // ...
  let s = Struct { value: 1234 };
  let o = s.test();  // o contains a reference to s
  // ...do something with o...
}

I thought I could just put the whole thing inside a block and have it work without changing the block, but apparently that's not possible:

fn main() {
  // ...
  let span = span!(Level::INFO, "name");
  let (s, o) = {
    let _enter = span.enter();
    let s = Struct { value: 1234 };
    let o = s.test();  // o contains a reference to s
    (s, o)
  }
  // ...do something with o...
}

The issue with Span::enter is indeed that I need to drop it at some point, so if I'm not mistaken I need that separate block — I don't think there's a way to drop that Span object prematurely in Rust.

(The actual code deals with image data using the image crate, I reorganized it so it avoids keeping a reference, in the end it wasn't strictly required. I was simply surprised that I couldn't simply add a block and be done with it, so I'd like to understand the situation a little better...)

You can drop the span guard explicitly or create s outside of the scope.

1 Like

Ah, right, I forgot to mention that: creating s and o is relatively expensive in my actual code, hence the need for a profiling block. I wasn't aware you could drop in Rust, thanks for the pointer!

I'm still very unsure about how I'd approach a similar issue in the future, I think I will try some more things to get a better feel for this.