Hitting "undefined reference to `core::future::identity_future'" when working with associated types and async

Hey folks, I'm hitting a bit of a wall with this one.

Basically, I have some wrapping over reqwest and reqwest_middleware, and need it to be generic over the error types involved (since these libraries both return a the same Response on success, but different error types). The strange part is that cargo check reports no issues, but cargo build results in a linker error claiming part of core doesn't exist, which is surprising to me.

I've boiled the issue down to a minimum reproducing case (playground link), tearing out all dependencies and using just standard rust - it comes to about 130 lines of code, including some comments. It's a little weird looking since I tried to make the problem statement generic, rather than referencing anything specific to reqwest or similar. Also reproduced here:

// Imagine we have two "factories", both of which can produce two different "builders".
struct FactoryA;
struct FactoryB;
struct BuilderA;
struct BuilderB;

// Imagine each builder can produce either a Vec<u8>, or some builder-specific error.
pub enum ErrorA {
    A,
}

pub enum ErrorB {
    B,
}

// We have some trait to represent the notion of a "builder factory"
pub trait BuilderFactory {
    type Builder: BuilderInner;
    fn get_builder(&self) -> Builder<Self::Builder>;
}

// And a generic wrapper around the factory (imagine we have some logic that's generic across all factories)
pub struct FactoryWrapper<T: BuilderFactory> {
    pub inner: T,
}

// We also have a trait to represent the "builder"
pub trait BuilderInner: Send {
    type Error;
    fn build(self) -> Result<Vec<u8>, Self::Error>;
}

// And a generic wrapper around the builder
pub struct Builder<T: BuilderInner> {
    inner: T,
}

// We'll implement the BuilderFactory trait for each of our factories
impl BuilderFactory for FactoryA {
    type Builder = BuilderA;
    fn get_builder(&self) -> Builder<Self::Builder> {
        Builder { inner: BuilderA }
    }
}

impl BuilderFactory for FactoryB {
    type Builder = BuilderB;
    fn get_builder(&self) -> Builder<Self::Builder> {
        Builder { inner: BuilderB }
    }
}

// And implement the wrapper around our builders - imagine there is some async work done
// in this wrapper
impl<T: BuilderInner> Builder<T> {
    pub async fn build(self) -> Result<Vec<u8>, <T as BuilderInner>::Error> {
        self.inner.build()
    }
}

// We'll implement our builder trait for our builders
impl BuilderInner for BuilderA {
    type Error = ErrorA;

    fn build(self) -> Result<Vec<u8>, Self::Error> {
        Ok(vec![])
    }
}

impl BuilderInner for BuilderB {
    type Error = ErrorB;

    fn build(self) -> Result<Vec<u8>, Self::Error> {
        Ok(vec![])
    }
}

// Lets say we have a trait that is designed to let any implementer that can provide one of these factories
// easily "produce" something (imagine there is more to the builders, such that using them involves some steps)
// Since the builder wrapper does some async work, this must also be async
pub trait Produce<'a>: Sized + 'a {
    type Builder: BuilderFactory;

    fn get_factory(&self) -> &FactoryWrapper<Self::Builder>;

    fn produce(
        self,
    ) -> ::core::pin::Pin<
        Box<
            dyn ::core::future::Future<
                    Output = Result<
                        Vec<u8>,
                        <<Self::Builder as BuilderFactory>::Builder as BuilderInner>::Error,
                    >,
                > + ::core::marker::Send
                + 'a,
        >,
    >
    where
        Self: ::core::marker::Send,
    {
        Box::pin(async move {
            let builder = self.get_factory().inner.get_builder();
            let built = builder.build().await?;
            return Ok(built);
        })
    }
}

//Imagine we have one such "factory owner"
// We'll have some "context" that's generic across all factories
pub struct FactoryOwner<'a, Factory: BuilderFactory> {
    bridge: &'a FactoryWrapper<Factory>,
}

// And we'll have some trait we want to implement for our context
impl<'a, Builder: BuilderFactory> Produce<'a> for FactoryOwner<'a, Builder> {
    type Builder = Builder;

    fn get_factory(&self) -> &crate::FactoryWrapper<Self::Builder> {
        self.bridge
    }
}

// This all type checks and passes cargo check, but cargo build produces a linker error
fn main() {
    let bridge = FactoryWrapper { inner: FactoryA };
    let context = FactoryOwner { bridge: &bridge };
    context.produce();
}

Is this some simple mistake I'm making? And if so, should cargo check catch it? Or should I open an issue on the cargo issue tracker?

Thanks!

1 Like

Maybe it's this. You should comment on the issue.

Here's a further reduced version:

use core::future;

fn func<T>() -> impl Sized {}

trait Trait<'a> {
    type Assoc;

    fn call() {
        let _ = async {
            let _value = func::<Self::Assoc>();
            future::ready(()).await
        };
    }
}

impl Trait<'static> for () {
    type Assoc = ();
}

fn main() {
    <()>::call();
}

Curiously, to reproduce this issue, the trait must have a lifetime parameter, and the associated function must be defined in the trait, instead of the impl.

Also, a version using a generator, to show that the async lowering is not at fault:

#![feature(generators)]

fn wrapper<T>(value: T) -> T {
    value
}

fn func<T>() -> impl Sized {}

trait Trait<'a> {
    type Assoc;

    fn call() {
        let _ = wrapper(|()| {
            let _value = func::<Self::Assoc>();
            yield;
        });
    }
}

impl Trait<'static> for () {
    type Assoc = ();
}

fn main() {
    <()>::call();
}

The wrapper is also necessary to reproduce the issue.

EDIT: I bisected this regression to nightly-2021-09-28. It was most likely caused by PR 89285. But it was merged into 1.56.0, not 1.65.0, so it's probably a different issue from the one @quinedot linked to.

2 Likes

@LegionMammal978 Thanks for further reducing, I filed a new regression, really appreciate you folks taking a look at it. I'll mark this as solved with that, since any further discussion should probably happen in the tracking issue.

1 Like

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.