# Type erasure and dyn lifetime in a NonNull/Box struct field

**URL:** <https://users.rust-lang.org/t/type-erasure-and-dyn-lifetime-in-a-nonnull-box-struct-field/142830>\
**Category:** help\
**Created:** [October 4, 2026, 10:55pm UTC](https://users.rust-lang.org/t/type-erasure-and-dyn-lifetime-in-a-nonnull-box-struct-field/142830 "2026-10-04T22:55:01Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![rhansen](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/rhansen/32/54172_2.png) [@rhansen](https://users.rust-lang.org/u/rhansen)\
**Post date:** [October 4, 2026, 10:55pm UTC](https://users.rust-lang.org/t/type-erasure-and-dyn-lifetime-in-a-nonnull-box-struct-field/142830/1 "2026-10-04T22:55:01Z")

</div>

I'm new to Rust, and I am trying to understand `dyn` object lifetimes when inside a `Box` or `NonNull`. My goal is to write an adapter for a C API. I have a few C struct types:

```rust
#[repr(C)]
struct ConcreteA {
    // generated by bindgen
}

#[repr(C)]
struct ConcreteB {
    // generated by bindgen
}

```

that all implement the same trait:

```rust
trait Trait {
    // common methods
}

impl Trait for ConcreteA {
    // implementation calls C functions
}

impl Trait for ConcreteB {
    // implementation calls different C functions
}

```

The structs are allocated and initialized in C, so I created a generic "handle" smart pointer type that owns an instance of one of those structs:

```rust
use std::{ops::Deref, ptr::NonNull};

#[repr(transparent)]
struct ConcreteHandle<T: Trait>(NonNull<T>);

impl<T: Trait> ConcreteHandle<T> {
    // The real code calls into C to allocate and initialize the memory; using a
    // Box is for illustration.
    fn new(v: T) -> Self {
        Self(Box::into_non_null(Box::new(v)))
    }
}

impl<T: Trait> Drop for ConcreteHandle<T> {
    // The real code calls into C to free the memory; this is for illustration.
    fn drop(&mut self) {
        drop(unsafe { Box::from_non_null(self.0) });
    }
}

impl<T: Trait> Deref for ConcreteHandle<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        unsafe { self.0.as_ref() }
    }
}

```

Semantically, the handle more-or-less _is_ the underlying concrete object—the lifetime of the handle and the lifetime of the underlying object are tied together. All good so far.

My trouble starts when I try to create a type-erased handle (to make it easier to hold a heterogeneous collection), like this ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=783632d5a94823134b8d249d1f0f681e)):

```rust
#[derive(Debug)]
#[repr(transparent)]
struct TypeErasedHandle(NonNull<dyn Trait>);

impl<T: Trait> From<ConcreteHandle<T>> for TypeErasedHandle {
    fn from(h: ConcreteHandle<T>) -> Self {
        Self(NonNull::new(h.0.as_ptr()).unwrap())
    }
}

impl Drop for TypeErasedHandle {
    fn drop(&mut self) {
        drop(unsafe { Box::from_non_null(self.0) });
    }
}

impl Deref for TypeErasedHandle {
    type Target = dyn Trait;

    fn deref(&self) -> &Self::Target {
        unsafe { self.0.as_ref() }
    }
}

```

I get this error:

```plaintext
error[E0310]: the parameter type `T` may not live long enough
  --> src/main.rs:63:14
   |
63 | Self(NonNull::new(h.0.as_ptr()).unwrap())
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   | |
   | the parameter type `T` must be valid for the static lifetime...
   | ...so that the type `T` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound
   |
61 | impl<T: Trait + 'static> From<ConcreteHandle<T>> for TypeErasedHandle {
   | +++++++++

For more information about this error, try `rustc --explain E0310`.

```

If I do what the error suggests and add `'static` to the type bound, then the toy example compiles and runs, but I have several questions:

1. Doesn't adding `'static` (as suggested by the error) limit the usefulness of the `TypeErasedHandle` type? Doesn't it disallow conversion from a useful subset of `ConcreteHandle<T>`?

2. How can I get an intuitive understanding of what it means to add `+ 'static` to a type bound like this? I understand the use of `'static` for objects that are truly required to have indefinite lifetime, but that doesn't apply here; the lifetime should match the lifetime of the `TypeErasedHandle` struct. (Right?)

3. If I add a generic lifetime parameter like this:

4. ~~Should I store the output of [`pointer.to_raw_parts()`](https://doc.rust-lang.org/std/primitive.pointer.html#method.to_raw_parts-1) instead of the fat pointer itself?~~ (has the same problem)

5. Would you take a different approach altogether?

---

<div class="post-metadata">

**Author:** ![throwable-one](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/throwable-one/32/31948_2.png) [@throwable-one](https://users.rust-lang.org/u/throwable-one)\
**Post date:** [October 4, 2026, 11:24pm UTC](https://users.rust-lang.org/t/type-erasure-and-dyn-lifetime-in-a-nonnull-box-struct-field/142830/2 "2026-10-04T23:24:19Z")

</div>

I probably got something wrong, but If your struct implements `Deref` that means it is already a reference, so you could probably use it dynamically e.g:

```rust
    let a_ref: &dyn Trait = &*ConcreteHandle::new(ConcreteA {});
    let b_ref: &dyn Trait = &*ConcreteHandle::new(ConcreteB {});
    let list:Vec<&dyn Trait> = vec![a_ref, b_ref];

```

Why do you need a separate struct `TypeErasedHandle` for it?

PS: Could probably also be

```rust
impl<T: Trait> AsRef<T> for ConcreteHandle<T> {
    fn as_ref(&self) -> &T {
        unsafe { self.0.as_ref() }
    }
}
////
    let handle = ConcreteHandle::new(ConcreteA {});
    let a_ref: &dyn Trait = handle.as_ref();

```

---

<div class="post-metadata">

**Author:** ![rhansen](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/rhansen/32/54172_2.png) [@rhansen](https://users.rust-lang.org/u/rhansen)\
**Post date:** [October 4, 2026, 11:36pm UTC](https://users.rust-lang.org/t/type-erasure-and-dyn-lifetime-in-a-nonnull-box-struct-field/142830/3 "2026-10-04T23:36:31Z")

</div>

Thank you for your answer.

> [@throwable-one](#):
>
> Why do you need a separate struct `TypeErasedHandle` for it?

I want something that owns the underlying object. The `Deref` is for implementation convenience, not because the handle is semantically equivalent to a reference. I could alternatively do something like this:

```rust
struct TypeErasedHandle(NonNull<dyn Trait>);

impl<T: Trait> From<ConcreteHandle<T>> for TypeErasedHandle { /* ... */ }

impl Drop for TypeErasedHandle { /* ... */ }

impl Trait for TypeErasedHandle {
    // forward every method to `self.0`
}

```

---

<div class="post-metadata">

**Author:** ![nerditation](https://avatars.discourse-cdn.com/v4/letter/n/e79b87/32.png) [@nerditation](https://users.rust-lang.org/u/nerditation)\
**Post date:** [October 5, 2026, 12:10am UTC](https://users.rust-lang.org/t/type-erasure-and-dyn-lifetime-in-a-nonnull-box-struct-field/142830/4 "2026-10-05T00:10:20Z")

</div>

> [@rhansen](#):
>
> My trouble starts when I try to create a type-erased handle (to make it easier to hold a heterogeneous collection)

technically, `dyn Trait` is NOT a single type, but a type constructor: the real type always has a lifetime attached to it. see:

> **[The trait object lifetime - dyn Trait overview - Learning Rust](https://quinedot.github.io/rust-learning/dyn-trait-overview.html?highlight=dyn#the-trait-object-lifetime)**
>
> dyn Trait is a compiler-provided type which implements Trait. Sized implementors of Trait can be coerced to be a dyn Trait, erasing the original base type in the process. Different implementations of Trait may have different sizes, and as a result,...

the usual notation are convenient shorthands, e.g. `&'a dyn Trait` is short for `&'a dyn Trait + 'a`, `Box<dyn Trait>` is short for `Box<dyn Trait + 'static>`.

`NonNull<dyn Trait>` is short for `NonNull<dyn Trait + 'static>`, similar to the `Box` case.

in your example, it's ok to put the `'static` bounds on the `ConcreteHandle` since the pointee of the concrete type does not contain any rust lifetimes. this should compile:

```rust
impl<T: Trait + 'static> From<ConcreteHandle<T>> for TypeErasedHandle {
    //...
}

```

---

<div class="post-metadata">

**Author:** ![kpreid](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/kpreid/32/22420_2.png) [@kpreid](https://users.rust-lang.org/u/kpreid)\
**Post date:** [October 5, 2026, 12:10am UTC](https://users.rust-lang.org/t/type-erasure-and-dyn-lifetime-in-a-nonnull-box-struct-field/142830/5 "2026-10-05T00:10:44Z")

</div>

> [@rhansen](#):
>
> Semantically, the handle more-or-less _is_ the underlying concrete object—the lifetime of the handle and the lifetime of the underlying object are tied together. All good so far.

The phrase “the lifetime of” is a great way to get confused when talking about Rust, and the rest of your post demonstrates this confusion. The elements of Rust that are called “lifetimes” have almost nothing to do with how long a _value_ exists; rather, they refer to how long a _borrow_ (an invocation of the `&` operator or equivalent) is active. I strongly recommend that you avoid the phrase “the lifetime of” and try to find something more specific to say in each case instead.

> [@rhansen](#):
>
> 1. Doesn't adding `'static` (as suggested by the error) limit the usefulness of the `TypeErasedHandle` type? Doesn't it disallow conversion from a useful subset of `ConcreteHandle<T>`?

Not unless one of the types that implements the trait _contains a lifetime_, which usually only happens when the values of that type contain references somewhere, which I would imagine is probably impossible or highly atypical for bindgen-generated structs. (By “contain” I mean, for example, that `SomeType<'a>` contains the lifetime `'a`.)

> [@rhansen](#):
>
> 1. How can I get an intuitive understanding of what it means to add `+ 'static` to a type bound like this?

A lifetime bound on a type means that the _type_ cannot _contain_ a lifetime that is shorter than that bound. Most types meet most lifetime bounds by not containing any lifetimes (in particular, not containing any references). For example, the bound `u32: 'static` holds because `u32` contains no references. Therefore, you can coerce a `u32` to a `dyn Trait + 'static` if `u32` also implements the trait. The same logic should apply to your generated struct.

> [@rhansen](#):
>
> 1. Would you take a different approach altogether?

You should probably try generalizing `ConcreteHandle` instead of making a separarte `TypeErasedHandle`:

```rust
#[repr(transparent)]
struct ConcreteHandle<T: Trait + ?Sized>(NonNull<T>);

```

This change allows `ConcreteHandle<dyn Trait + 'static>` to exist. This might not work out for your needs, but if it does, it’s much simpler.

---

<div class="post-metadata">

**Author:** ![rhansen](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/rhansen/32/54172_2.png) [@rhansen](https://users.rust-lang.org/u/rhansen)\
**Post date:** [October 5, 2026, 3:24am UTC](https://users.rust-lang.org/t/type-erasure-and-dyn-lifetime-in-a-nonnull-box-struct-field/142830/6 "2026-10-05T03:24:23Z")

</div>

> [@kpreid](#):
>
> “lifetimes” have almost nothing to do with how long a _value_ exists; rather, they refer to how long a _borrow_ (an invocation of the `&` operator or equivalent) is active.

That's incredibly helpful. To help me reform my mental model, can you confirm whether the following statements are correct:

- A reference of type `&'a T` (or `&'a mut T)` borrows a `T` value, and can be safely dereferenced until `'a`. (The referenced `T` value will remain valid until at least `'a`.)
- A value of type `Foo<'a>` can be safely used until `'a`. (The value contains—directly or indirectly—zero or more borrows, possibly [phantom](https://doc.rust-lang.org/std/marker/struct.PhantomData.html), and all of the borrows can be safely dereferenced until `'a`.)
- A type bound like `T: 'a` means that the `T` value must remain valid until at least `'a`. (If the value contains any borrows, all of those borrows can be safely dereferenced until `'a`.)
- A type bound like `T: 'a + 'b` means that the `T` value must remain valid until at least the longer of `'a` and `'b`.

It's unclear to me what the type `Foo<'a, 'b>` says about how long it is safe to use a value of that type. My guess: The value can be safely used until the sooner of `'a` and `'b`. But maybe it's more subtle than that if an access can be limited to a certain part of the value?

> [@kpreid](#):
>
> > [@rhansen](#):
> >
> > 1. Doesn't adding `'static` (as suggested by the error) limit the usefulness of the `TypeErasedHandle` type? Doesn't it disallow conversion from a useful subset of `ConcreteHandle<T>`?
> 
> Not unless one of the types that implements the trait _contains a lifetime_, which usually only happens when the values of that type contain references somewhere,
> 
> [...]
> 
> A lifetime bound on a type means that the _type_ cannot _contain_ a lifetime that is shorter than that bound.

So adding a `'static` lifetime bound doesn't mean, "the value must be alive indefinitely", but rather, "the value must not have a built-in expiration date." Correct?

> [@kpreid](#):
>
> You should probably try generalizing `ConcreteHandle` instead of making a separarte `TypeErasedHandle`:
> 
> ```rust
> #[repr(transparent)]
> struct ConcreteHandle<T: Trait + ?Sized>(NonNull<T>);
> 
> ```
> 
> This change allows `ConcreteHandle<dyn Trait + 'static>` to exist.

Interesting idea! I'll play with that and see how it goes. Thank you!

---

<div class="post-metadata">

**Author:** ![quinedot](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/quinedot/32/17693_2.png) [@quinedot](https://users.rust-lang.org/u/quinedot)\
**Post date:** [October 5, 2026, 4:16am UTC](https://users.rust-lang.org/t/type-erasure-and-dyn-lifetime-in-a-nonnull-box-struct-field/142830/7 "2026-10-05T04:16:48Z")

</div>

> [@rhansen](#):
>
> 1. Doesn't adding `'static` (as suggested by the error) limit the usefulness of the `TypeErasedHandle` type? Doesn't it disallow conversion from a useful subset of `ConcreteHandle<T>`?

It means `T` can't contain any lifetimes that aren't `'static`, which generally means that `T` (or its fields, etc) is not borrowing anything.\[1\]

> [@rhansen](#):
>
> 1. How can I get an intuitive understanding of what it means to add `+ 'static` to a type bound like this?

The first step is to recognize that Rust lifetimes (those `'a` things) generally denote the duration of some borrow, and not the liveness scope of any particular value. They're a compile-time, descriptive, type-level property. They're not a runtime property or a property of any particular value.

The second step is to know that lifetime bounds are defined syntactically. For example,

```rust
Ref<'a, T>: 'b

```

holds if and only if `'a: 'b` and `T: 'b`. If `T` happens to be a generic, and whatever it expands to happens to have lifetimes or more type generics, this repeats recursively.

For `'static` in particular, it means that the only lifetime that may appear in the fully expanded left-hand side is `'static`.

As a concrete example, `String: 'static` as it trivially upholds that property. That doesn't mean that `String`s never drop and deallocate, because we're talking about type level properties, not value liveness scopes.

If you have a `dyn Trait + 'b`, that means the type of the original value which coerced to `dyn Trait + 'b` met a `: Trait + 'b` bound.

> [@rhansen](#):
>
> - A reference of type `&'a T` (or `&'a mut T)` borrows a `T` value, and can be safely dereferenced until `'a`. (The referenced `T` value will remain valid until at least `'a`.)

Yes... but that answer may be misleading. _Values_ of type `&'a T` don't have to _stick around_ for all of `'a`, and generally speaking the uses of references determine the lifetime in their type. The lifetime in the type does not determine how long the value is around and available to actually get dereferenced.

Let me try to make that more clear.

Note that usually if you can name the lifetime `'a`, like in this function body...

```rust
fn example<'a>(s: &'a str) -> &'a str { s }

```

...the lifetime denotes some duration that lasts at least until the function has returned. Within the function this means little beyond "it's longer than the function body".\[2\] That is not irrelevant -- it means for example that you can never borrow a local for `'a` (as all locals drop or move by the end of the function call, which are incompatible with being borrowed).

But it doesn't tell you how long the return _value_ will be around / dereferenceable. The practical meaning of the signature is "so long as the return value is in use, `*s` remains borrowed". In effect how the return value is used will determine the borrow duration of `*s` in the context of the call site.

Lifetimes you can't name, like the borrow duration of locals, are inferred based on how the borrowing value is used, and how values with types which are related by a borrowing relationship get used.\[3\] So to continue the example...

```rust
fn ex2() {
    let mut s = String::new(); // L1
    let b = example(&*s); // L2: let b: &'x = example::<'y>(&*s)
    // s.push('!'); // L3
    println!("{b}"); // L4
    s.push('!'); // L5
}

```

A borrow of `*s` gets created on L2, this borrow ends up connected to the lifetime in the type of `b`,\[4\] uses of `b` keep the lifetime in the type of `b` active which in turn keeps the borrow of `*s` active.

So `*s` is borrowed on L3 and L4 due to the use in L4. If you uncomment L3 you will get a borrow checker error. But nothing requires the borrow to be active on `L5`, so [this compiles as-is.](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=187532edf51f0dd0b666b72e80966e97)

Where we used `b` determined the lifetimes in question. We didn't assign some lifetime to `b`'s type that said `b` could be dereferenced on L4 but not L5.

> [@rhansen](#):
>
> - A value of type `Foo<'a>` can be safely used until `'a`.

Yes, modulo the same notes as above.

> [@rhansen](#):
>
> - (The value contains—directly or indirectly—zero or more borrows, possibly [phantom](https://doc.rust-lang.org/std/marker/struct.PhantomData.html), and all of the borrows can be safely dereferenced until `'a`.)

Generally yes and probably how I'd recommend thinking about it. Lifetimes can technically appear without a borrow being made, purely through the type system, but I wouldn't worry about it when learning.

> [@rhansen](#):
>
> - A type bound like `T: 'a` means that the `T` value must remain valid until at least `'a`. (If the value contains any borrows, all of those borrows can be safely dereferenced until `'a`.)

Definitely no, again consider that `String: 'static`. It means the type `T` is valid for `'a`. It also means that any lifetime `'b` or other generic `'U` in the type represented by `T` meets a `'a` bound.

As for the parenthetical, "yes... but that may be misleading", the same as talking about references themselves.

> [@rhansen](#):
>
> - A type bound like `T: 'a + 'b` means that the `T` value must remain valid until at least the longer of `'a` and `'b`.

It means both `T: 'a` and `T: 'b` must hold.

Think of `'a` and `'b` as sets -- it may be the case that ~~`'a` is not longer than `'b` and `'b` is not longer than `'a`~~ `'b` is not a subset of `'a` and `'a` is not a subset of `'b`.

With that mindset, `'a + 'b` is the union of `'a` and `'b`.

> [@rhansen](#):
>
> It's unclear to me what the type `Foo<'a, 'b>` says about how long it is safe to use a value of that type.

If the code is sound, it's generally fine to use values whenever the type is valid, and the compiler throws an error if you try to use a value in a place where its type isn't valid. In this case, that would be the _intersection_ of `'a` and `'b`.

In practice, uses of values of type `Foo<'a, 'b>` usually determine what `'a` and `'b` can possibly be or result in borrow checker errors, like the examples above.

> [@rhansen](#):
>
> So adding a `'static` lifetime bound doesn't mean, "the value must be alive indefinitely", but rather, "the value must not have a built-in expiration date." Correct?

It's a type-level property, not a value property. It does effectively say "the value contains no borrows". (With some small print like "except maybe borrows of static or leaked memory.") It does mean "you can coerce the value to a `dyn Trait + 'static`" (assuming `Ty: Trait`). It does mean, "you can leak values into a `&'static mut _` and keep it them around the rest of the runtime if you want to".

* * *

1. At least, not in a way one usually cares about. It might technically be borrowing static or leaked memory. 

2. especially since no relationship with other lifetimes is being expressed here 

3. Even more accurately, no concrete lifetime need be computed. All that's required is proving some lifetime that doesn't have conflicts exists. 

4. `'y: 'x` -- "if `'x` is active, so is `'y`"

---

<div class="post-metadata">

**Author:** ![kpreid](https://sea1.discourse-cdn.com/flex019/user_avatar/users.rust-lang.org/kpreid/32/22420_2.png) [@kpreid](https://users.rust-lang.org/u/kpreid)\
**Post date:** [October 5, 2026, 4:16am UTC](https://users.rust-lang.org/t/type-erasure-and-dyn-lifetime-in-a-nonnull-box-struct-field/142830/8 "2026-10-05T04:16:50Z")

</div>

> [@rhansen](#):
>
> To help me reform my mental model, can you confirm whether the following statements are correct:
> 
> - A reference of type `&'a T` (or `&'a mut T)` borrows a `T` value, and can be safely dereferenced until `'a`. (The referenced `T` value will remain valid until at least `'a`.)

True.

> [@rhansen](#):
>
> - A value of type `Foo<'a>` can be safely used until `'a`. (The value contains—directly or indirectly—zero or more borrows, possibly [phantom](https://doc.rust-lang.org/std/marker/struct.PhantomData.html), and all of the borrows can be safely dereferenced until `'a`.)

True.

> [@rhansen](#):
>
> - A type bound like `T: 'a` means that the `T` value must remain valid until at least `'a`. (If the value contains any borrows, all of those borrows can be safely dereferenced until `'a`.)

“The `T` value must remain valid” is false or confusing depending on exactly what you mean.

This bound means that a `T`’s owner _may_ keep it around and use it until `'a` ends. It does not obligate the owner to keep it around until `'a` ends.

> [@rhansen](#):
>
> - A type bound like `T: 'a + 'b` means that the `T` value must remain valid until at least the longer of `'a` and `'b`.

Well, this contains the same confusion as the previous point, but other than that, yes. However, note that this is a simple logical consequence of “`T: 'a` _and_ `T: 'b`”. That’s all the `+` means.

> [@rhansen](#):
>
> It's unclear to me what the type `Foo<'a, 'b>` says about how long it is safe to use a value of that type. My guess: The value can be safely used until the sooner of `'a` and `'b`. But maybe it's more subtle than that if an access can be limited to a certain part of the value?

No, you have it right. However, there may be functions which take a `Foo<'a, 'b>` and return `Bar<'a>` or `Bar<'b>`, in which case the longer borrows `Foo` had may outlive the shorter of the two lifetimes, even though `Foo` doesn’t.

> [@rhansen](#):
>
> So adding a `'static` lifetime bound doesn't mean, "the value must be alive indefinitely", but rather, "the value must not have a built-in expiration date." Correct?

Exactly!
