Putting things inside a struct gives me lifetime errors

First of all, I know it's much better to create minimal reproducible examples, but I don't have idea on where this problem comes from. I've cleared much of the code for this question.

#[derive(Debug)]
pub struct Set<'a, 'b: 'a, 'c: 'a + 'b> {
    sockets: ManagedSlice<'a, Option<Item<'b, 'c>>>
}

impl<'a, 'b: 'a, 'c: 'a + 'b> Set<'a, 'b, 'c> {
    /// Create a socket set using the provided storage.
    pub fn new<SocketsT>(sockets: SocketsT) -> Set<'a, 'b, 'c>
            where SocketsT: Into<ManagedSlice<'a, Option<Item<'b, 'c>>>> {
        let sockets = sockets.into();
        Set {
            sockets: sockets
        }
    }

pub struct TunSmolStack<'a, 'b, 'c> {
    sockets: Set<'a, 'b, 'c>,
}

impl<'a, 'b, 'c> TunSmolStack<'a, 'b, 'c> {
    pub fn new(interface_name: String) -> Result<TunSmolStack<'a, 'b, 'c>, u32> {
        let socket_set = Set::new(vec![]);
        Ok(TunSmolStack{
            sockets: socket_set,
        })
    }

Here's what I get:

cannot infer an appropriate lifetime for lifetime parameter 'b due to conflicting requirements

note: expected virtual_tun::smol_stack::TunSmolStack<'_, 'b, 'c>
found virtual_tun::smol_stack::TunSmolStack<'_, '_, '_>
note: but, the lifetime must be valid for the static lifetime...rustc(E0495)
Peek Problem (Alt+F8)

I'm trying to interpret the error. I don't understand lifetimes too much, but, let's try. The bunch of _ means I'm not caring about the lifetimes of things I'm putting inside the TunSmolStack? I know that I create socket_set in new which has very short lifetime but since I move it to the inside of the struct, its lifetime should be as long as the struct's.

Maybe it has something to do with the vec! that I pass? Well, the object passed has to implement Into<ManagedSlice<'a, Option<Item<'b, 'c>>>>. I didnt find an implementation for that on std::vec's page but the compiler didnt complain either.

Wow, you've got a lot of lifetimes going on here. Are you sure you shouldn't be using owned types in your structs?

I can't tell exactly what caused the error because you didn't post the full error, nor a full example that produces the error.

1 Like

the full error is that, at least on my intellisense. Gonna try to compile but need to clean errors on other files.

What you mean by owned types? I thought I were using owned types: the struct owns them because I pass them by moving.

You may need to run the cargo build command to actually see the full error. Your IDE probably doesn't show it.

With owned types, I mean e.g. String is a type that takes ownership and &str is a type that borrows, so you should probably prefer String. Any type that is annotated with a lifetime parameter (e.g. a reference, but also a struct with a reference field) is a type that borrows something else.

Borrowing is rarely something you want in structs, because "borrow" is telling the compiler that the fields annotated with lifetimes contain pointers to data owned by some other variable than the struct, and the compiler will ensure that your struct cannot outlive the thing it has a pointer to.

1 Like

I thought of borrowing as something that happens very fast like function calls. Makes sense for functions to borrow. But a Set is a container. How can cotainers borrow data?

You typically do not want containers to borrow data, but by using types with lifetime markers, that is what you're telling the compiler you want. Here's an example of a struct with a reference field:

#[derive(Debug)]
struct ContainsABorrow<'a> {
    a: &'a str,
}

fn main() {
    let a = "foo".to_string();
    
    // this struct contains a reference to the variable a
    let brw = ContainsABorrow {
        a: &a,
    };
    
    // this is ok, because a exists, so the reference is still valid
    println!("{:?}", brw);
    
    drop(a);
    
    // but this will fail because a has been destroyed
    println!("{:?}", brw);
}

This fails with this error:

error[E0505]: cannot move out of `a` because it is borrowed
  --> src/main.rs:18:10
   |
12 |         a: &a,
   |            -- borrow of `a` occurs here
...
18 |     drop(a);
   |          ^ move out of `a` occurs here
...
21 |     println!("{:?}", brw);
   |                      --- borrow later used here

Similarly trying to make a constructor also fails:

impl<'a> ContainsABorrow<'a> {
    fn new() -> Self {
        // This will not work. Self only borrows from a, so the string
        // is destroyed when `new` returns, meaning that we can't return
        // a reference to a.
        let a = "bar".to_string();
        Self {
            a: &a
        }
    }
}

with the error

error[E0515]: cannot return value referencing local variable `a`
  --> src/main.rs:30:9
   |
30 | /         Self {
31 | |             a: &a
   | |                -- `a` is borrowed here
32 | |         }
   | |_________^ returns a value referencing data owned by the current function

playground

This post also has some examples of what full error messages look like.

2 Likes

Owning/borrowing is not for performance. It's tied to semantics of the code, which is why you're getting errors about the code not making sense.

And borrowing does not make things faster. For example &i32 and Box<i32> have identical representation in structs, but one makes the struct temporary, and the other allows the struct to be used without causing lifetime errors. In some cases use of borrowed values is actually slower (e.g. &String is slower than String due to double indirection, &bool is slower than bool due to 8x increase in size and indirection).

Don't try to make code faster by borrowing where you don't have to. This will make Rust incredibly painful to use, and not any faster.

2 Likes

thanks, I undrstood your 2 examples. However in my case I don't hold references. Instead I try to hold the actual object in the struct. This is what causes me confusion.

Here's the full error:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'b` due to conflicting requirements
  --> src/virtual_tun/smol_stack.rs:26:12
   |
26 |         Ok(TunSmolStack{
   |            ^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'b` as defined on the impl at 16:10...
  --> src/virtual_tun/smol_stack.rs:16:10
   |
16 | impl<'a, 'b, 'c> TunSmolStack<'a, 'b, 'c> {
   |          ^^
note: ...so that the expression is assignable
  --> src/virtual_tun/smol_stack.rs:26:12
   |
26 |           Ok(TunSmolStack{
   |  ____________^
27 | |             //device: device,
28 | |             sockets: socket_set,
29 | |             //interface: interface,
30 | |         })
   | |_________^
   = note: expected  `virtual_tun::smol_stack::TunSmolStack<'_, 'b, 'c>`
              found  `virtual_tun::smol_stack::TunSmolStack<'_, '_, '_>`
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `std::option::Option<virtual_tun::interface::smoltcp::socket::SocketSetItem<'_, '_>>` will meet its required lifetime bounds
  --> src/virtual_tun/smol_stack.rs:22:26
   |
22 |         let socket_set = SocketSet::new(vec![]);
   |                          ^^^^^^^^^^^^^^

It is complaining about SocketSet. Ok, possibly something related to the inner objects of the socket_set not living enough? Why it talks about static lifetime?

PS: putting my mouse on top of socket_set on

let socket_set = SocketSet::new(vec![]);

gives this type for the variable:

virtual_tun::interface::smoltcp::socket::SocketSet<'a, 'static, 'static>

Well, the lifetime 'a for the SocketSet, as defined here:

    pub struct SocketSet<'a, 'b: 'a, 'c: 'a + 'b> {
        sockets: ManagedSlice<'a, Option<Item<'b, 'c>>>
    }

is the lifetime of the thing stored inside ManagedSlice, which is Option<Item<'b, 'c>>

So, the anonymous vec that I pass to the constructor of SocketSet is converted into a reference to slice with lifetime 'a, and the things inside the slice have static lifetime. Don't know why, but they have. I guess the problem is that it requires that 'b from SmolStack should be 'static?

by fast I meant that the function uses the borrow really fast (some milliseconds) and then "frees" the borrow. While a container like Set would hold the reference, thus makes less sense to borrow, more like the Set owns the object

It looks like your struct has three lifetimes. Structs with lifetimes have all the same challenges that references do, since the compiler most confirm that the struct holding that struct must outlive all those lifetimes. I'd suggest revising your TunSmolStack to not hold references (or whatever is giving it those lifetimes).

that is the main problem. The TunSmolStack itself holds no reference, it holds objects. These objects are those who hold the references, and I cannot do anything about it, they're from a library.

So, I really need to hold them somewhere, because I'm gonna instantiate some TunSmolStack structs through C and call them, so somebody must hold these data in the Rust side.

If the objects are created in C they almost can't have references. What library is it that creates them?

no, I need to create TunSmolStack structs by calling Rust code from C, then I can call methods in the TunSmolStack from C, you know? C uses Rust. (actually C++)

It's this lib https://github.com/smoltcp-rs/smoltcp

I see, so you're using rust to create a C library based on smoltcp, which itself does no heap allocation, and therefore uses lifetimes in ways that I don't recommend that beginners attempt, and that most that coders learn to avoid. This does sound like a tricky problem.

It might be interesting to see the boundary between C/C++ and rust here. I'm not convinced that it can be safe to deal with objects like this across the ffi, since C can violate the borrow rules enforced by the rust compiler. Maybe if the C++ code is single threaded?

yes I plan to leave it single threaded but it also may not be needed.

See, SmolTcp is an IP stack, I just need it to do one thing: process packets and receive answers. So after it is configured properly, it's just a matter of sending and receiving packets to it. Just 2 function calls which I can write patiently and be very careful about safety.

I really tried to keep things in C++ only but the only good C++ stack I found (aipstack) did some really really really crazy templating and metaprogramming. Even though I understood it, since I'm doing something open source, I didn't want things to be impossible to read, and mainly audit. C has some stacks but they're mostly ports of FreeBSD or Linux stacks, are not portable and since C is very unsafe I didn't bother to try.

Context: I want to create OpenVPN sockets and send packets as a library. Currently, OpenVPN is simply a library that creates a TUN device on the operating system, and then I need to send packets through this tun. I want instead to send packets directly. I already made this work but OpenVPN transports IP packets, it does not care about TCP or UDP, so all the TCP responses and etc must be handled by some IP stack, so that's why I choose SmolTCP, because it's Rust and it's easy to interface with C/C++. But why I need to send packets programatically instead of interacting with the system's tun device? Well, Android will only let one OpenVPN connection, you can't make 2 or more simultaneously. iPhone probably wont allow also.

if someone has some idea on this it'd help me very much, still couldn't figure out whats hapening

No Errors for me.

// shim
pub struct Socket<'a, 'b: 'a> {
    _a: &'a str,
    _b: &'b str,
}

pub enum ManagedSlice<'a, T> {
    Borrowed(&'a mut [T]),
    Owned(Vec<T>),
}

impl<T> From<Vec<T>> for ManagedSlice<'_, T> {
    fn from(v: Vec<T>) -> Self {
        ManagedSlice::Owned(v)
    }
}
// shim end

pub struct Item<'a, 'b: 'a> {
    socket: Socket<'a, 'b>,
    refs: usize,
}

pub struct Set<'a, 'b: 'a, 'c: 'a + 'b> {
    sockets: ManagedSlice<'a, Option<Item<'b, 'c>>>,
}

impl<'a, 'b: 'a, 'c: 'a + 'b> Set<'a, 'b, 'c> {
    /// Create a socket set using the provided storage.
    pub fn new<SocketsT>(sockets: SocketsT) -> Set<'a, 'b, 'c>
    where
        SocketsT: Into<ManagedSlice<'a, Option<Item<'b, 'c>>>>,
    {
        let sockets = sockets.into();
        Set { sockets: sockets }
    }
}

pub struct TunSmolStack<'a, 'b, 'c> {
    sockets: Set<'a, 'b, 'c>,
}

impl<'a, 'b, 'c> TunSmolStack<'a, 'b, 'c> {
    pub fn new(interface_name: String) -> Result<TunSmolStack<'a, 'b, 'c>, u32> {
        let socket_set = Set::new(vec![]);
        Ok(TunSmolStack {
            sockets: socket_set,
        })
    }
}

Found The Cuplrit
Managed Slice From Vec<T> demands T: 'static

So Option<Item<'b, 'c>> must have 'static lifetime, so Item<'b, 'c> must have 'static, and hence 'b and 'c are inferred as 'static.

The bug seems to be fixed in rust-managed github repo.

Help: Consider replacing
managed = "0.7" with
managed = { git = "https://github.com/smoltcp-rs/rust-managed" } in own Cargo.toml file
or using a local cloned repo of managed to avoid breaking changes

thank you so much, it makes sense and worked for me.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.