bon icon indicating copy to clipboard operation
bon copied to clipboard

`#[builder]` with higher-rank trait bounds can cause confusing errors

Open Chaoses-Ib opened this issue 6 months ago • 3 comments

With #[builder]:

#[builder]
pub fn f<RefIntoIter, Paths, P>(#[builder(start_fn)] paths: Paths)
where
    Paths: AsRef<RefIntoIter> + Send + 'static,
    for<'a> &'a RefIntoIter: IntoIterator<Item = P>,
    P: AsRef<Path>,
{
    ()
}

#[test]
fn test() {
    let paths = vec![PathBuf::from("test.txt")];
    f::<Vec<_>, _, _>(paths).call();
}

Calling any setters and call() will cause a "no method named ..." error:

error[E0599]: no method named `call` found for struct `FBuilder<Vec<PathBuf>, Vec<PathBuf>, &PathBuf>` in the current scope
   --> src\time.rs:341:34
    |
328 |     #[builder]
    |     ---------- method `call` not found for this struct
...
341 |         f::<Vec<_>, _, _>(paths).call();
    |                                  ^^^^ method not found in `FBuilder<Vec<PathBuf>, Vec<PathBuf>, &PathBuf>`
    |
    = note: the method was found for
            - `FBuilder<RefIntoIter, Paths, P, S>`
    = help: items from traits can only be used if the trait is implemented and in scope
    = note: the following trait defines an item `call`, perhaps you need to implement it:
            candidate #1: `Fn`

While without #[builder]:

pub fn f<RefIntoIter, Paths, P>(paths: Paths)
where
    Paths: AsRef<RefIntoIter> + Send + 'static,
    for<'a> &'a RefIntoIter: IntoIterator<Item = P>,
    P: AsRef<Path>,
{
    ()
}

#[test]
fn test() {
    let paths = vec![PathBuf::from("test.txt")];
    f::<Vec<_>, _, _>(paths);
}
error[E0308]: mismatched types
   --> src\time.rs:340:9
    |
340 |         f::<Vec<_>, _, _>(paths);
    |         ^^^^^^^^^^^^^^^^^ one type is more general than the other
    |
    = note: expected reference `&'a PathBuf`
               found reference `&PathBuf`

It's much clearer to see there is a 'a missed.

Chaoses-Ib avatar Jun 20 '25 11:06 Chaoses-Ib

Hi! Thank you for reporting this. This one's quite difficult to fix. I expanded the macro code (like shown in the troubleshooting demo) and tried to move the where bounds from the impl block to the individual method on the builder and remove the where bounds from the struct and it produced a better error:


error[E0308]: mismatched types
   --> crates/sandbox/src/main.rs:150:19
    |
150 |     let builder = f::<Vec<_>, _, _>(paths);
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
    |
    = note: expected reference `&'a std::path::PathBuf`
               found reference `&std::path::PathBuf`

error[E0308]: mismatched types
   --> crates/sandbox/src/main.rs:152:5
    |
152 |     builder.call();
    |     ^^^^^^^^^^^^^^ one type is more general than the other
    |
    = note: expected reference `&'a std::path::PathBuf`
               found reference `&std::path::PathBuf`
note: the lifetime requirement is introduced here
   --> crates/sandbox/src/main.rs:102:47
    |
102 |         for<'a> &'a RefIntoIter: IntoIterator<Item = P>,
    |                                               ^^^^^^^^

However, the logic of keeping the bounds on the builder's struct protects from some other complex generics relationships, so not adding the bounds to the builder's struct is probably not worth the problems it may cause. Also by moving the where bounds from the impl block to the individual method we increase the noise in rustdoc rendered documentation for the methods - now rustdoc will copy the same where bound for every method...

I wish there was a way to fix this without harming the rustdoc output

Veetaha avatar Jun 20 '25 12:06 Veetaha

Also, a workaround - if you temporarily remove the final call() method - you'll get a nice error

error[E0308]: mismatched types
  --> crates/sandbox/src/main.rs:14:5
   |
14 |     f::<Vec<_>, _, _>(paths); // .call()
   |     ^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected reference `&'a std::path::PathBuf`
              found reference `&std::path::PathBuf`

Veetaha avatar Jun 20 '25 12:06 Veetaha

Thanks for your fast and detailed reply. Besides the error message itself, because lifetime checks happen after name resolution, calling the with and without #[builder] versions together will make rustc only report the "no method named ..." error. This had led me to the wrong direction and costed me some time to realize the root cause. I just want to share this to help others that may come into the same problem.

I agree moving the where bounds is not ideal. This is a minor problem in my view. Probably just having an issue for search is good enough.

Chaoses-Ib avatar Jun 20 '25 14:06 Chaoses-Ib