elastio / bon

Next-gen compile-time-checked builder generator, named function's arguments, and more!
https://bon-rs.com
Apache License 2.0
1.34k stars 19 forks source link

Trait bounds with lifetimes and E0392 "lifetime parameter never used" #206

Closed tonywu6 closed 2 days ago

tonywu6 commented 2 days ago

First of all thank you for this wonderful library!

Version

> cargo tree | grep bon
├── bon v3.0.0
│   ├── bon-macros v3.0.0 (proc-macro)

Description

The following doesn't compile:

pub trait SomeTrait<'a> {
    fn borrow_stuff(stuff: &'a str) -> Self;
}

#[bon::builder]
pub fn some_func<'a, T: SomeTrait<'a>>(arg1: T) -> T { arg1 }
error[E0392]: lifetime parameter `'a` is never used
 --> src/lib.rs:6:18
  |
6 | pub fn some_func<'a, T: SomeTrait<'a>>(arg1: T) -> T {
  |                  ^^ unused lifetime parameter
  |
  = help: consider removing `'a`, referring to it in a field, or using a marker such as `std::marker::PhantomData`

As suggested by rustc, this could be worked around using PhantomData:

#[bon::builder]
pub fn some_func<'a, T: SomeTrait<'a>>(
    arg1: T,
    #[builder(default)] _lifetime: PhantomData<&'a ()>,
) -> T {
    arg1
}

Looking at the macro expansion:

pub struct SomeFuncBuilder<
    'a,
    T: SomeTrait<'a>,
    S: some_func_builder::State = some_func_builder::Empty,
> {
    __private_vinyl_phantom: ::core::marker::PhantomData<
        (fn() -> S, fn() -> ::core::marker::PhantomData<T>),
    >,
    __private_vinyl_named_members: (::core::option::Option<T>,),
}

The error (credit to this SO answer which explains it nicely) is expected: the borrow checker can't find anything in SomeFuncBuilder that is borrowing data for 'a, but it's confusing when appearing on function-like syntax (it certainly was for me!)

It'd be great if bon can detect such "unused" lifetimes and put them somewhere in the builder struct.

A note for the community from the maintainers

Please vote on this issue by adding a 👍 reaction to help the maintainers with prioritizing it. You may add a comment describing your real use case related to this issue for us to better understand the problem domain.

Veetaha commented 2 days ago

Thank you for reporting this!

The nuance is that it's possible for functions and impl blocks in Rust to have unused lifetimes, while it's not possible to have unused lifetimes in structs.

For example this compiles albeit with an unused_lifetimes warning:

fn sut<'a, 'b>() {}

struct Sut;
impl<'a, 'b> Sut {}

I didn't think of this edge case. The builder's internal phantom data needs to capture all lifetime parameters as &'a (), &'b (), ... to make sure they all are used even when no function's parameter references it.

The fix was released in a 3.0.1 patch.