Skip to content

Function monomorphizes to early #141836

Open
@lukahasch

Description

@lukahasch

I am currently trying to put together my own spin (which has probably already been done but eh) on compile time reflection in rust, and since variadic generics don't exist yet, been creating heterogenous struct-based lists. To actually do anything useful with such lists without runtime checking though you need to enforce trait bounds generically, which can (afaik) only be done through bounds on other Types.
When using these Bounds with a user defined struct they perform as expected (see below), when using them with functions however they get monomorphized on the first bound (at least that's my best guess).
Aka instead of some Object F with bounds FnMut<(A,)> + FnMut<(B,)>, I get only fn(A) -> _.

I tried this code:

#![feature(unboxed_closures)]

pub trait Function<T> {
    type Output;
    fn apply(&mut self, value: T) -> Self::Output;
}

pub trait Map<T> {
    type Output;
    fn map<'a>(&mut self, value: T) -> Self::Output;
}

#[derive(Debug, PartialEq, Hash, Clone, Copy)]
pub struct Single<T>(pub T);

#[derive(Debug, PartialEq, Hash, Clone, Copy)]
pub struct Sequence<T, R>(pub T, pub R);

impl<F, T> Map<Single<T>> for F
where
    F: Function<T>,
{
    type Output = Single<F::Output>;

    fn map<'a>(&mut self, value: Single<T>) -> Self::Output {
        Single(self.apply(value.0))
    }
}

impl<F, T, R> Map<Sequence<T, R>> for F
where
    F: Function<T>,
    F: Map<R>,
{
    type Output = Sequence<<F as Function<T>>::Output, <F as Map<R>>::Output>;

    fn map<'a>(&mut self, value: Sequence<T, R>) -> Self::Output {
        Sequence(self.apply(value.0), self.map(value.1))
    }
}

impl<F, T> Function<T> for F
where
    F: FnMut<(T,)>,
{
    type Output = <F as FnOnce<(T,)>>::Output;

    fn apply(&mut self, value: T) -> Self::Output {
        self(value)
    }
}

use std::ops::Add;

pub struct Manual;

impl<T> Function<T> for Manual
where
    T: Add<Output = T> + Copy,
{
    type Output = T;

    fn apply(&mut self, value: T) -> Self::Output {
        value + value
    }
}

fn function<T: Add<Output = T> + Copy>(v: T) -> T {
    v + v
}

fn main() {
    let t: Sequence<i32, Sequence<f32, Single<i8>>> = Sequence(1, Sequence(1.0, Single(1)));

    dbg!(Manual.map(t));
    dbg!(function.map(t));
}

I expected to see this happen:
function.map(t) should perform the same as Manual.map(t)

Instead, this happened:
error[E0631]: type mismatch in function arguments
--> src/main.rs:76:19
|
68 | fn function<T: Add<Output = T> + Copy>(v: T) -> T {
| ------------------------------------------------- found signature defined here
...
76 | dbg!(function.map(t));
| ^^^ expected due to this
|
= note: expected function signature fn(f32) -> _
found function signature fn(i32) -> _
note: required for fn(i32) -> i32 {function::<i32>} to implement Function<f32>
--> src/main.rs:42:12
|
42 | impl<F, T> Function for F
| ^^^^^^^^^^^ ^
43 | where
44 | F: FnMut<(T,)>,
| ----------- unsatisfied trait bound introduced here

Meta

Stable, Beta and Nightly all catch this though unboxed_closures is not yet stabilized.

rustc --version --verbose:

rustc 1.87.0 (17067e9ac 2025-05-09)
binary: rustc
commit-hash: 17067e9ac6d7ecb70e50f92c1944e545188d2359
commit-date: 2025-05-09
host: aarch64-apple-darwin
release: 1.87.0
LLVM version: 20.1.1

rustc +nightly --version --verbose:

rustc 1.89.0-nightly (70b3f4666 2025-05-30)
binary: rustc
commit-hash: 70b3f4666e24ce22fc32f5e357dbcf85d3254e63
commit-date: 2025-05-30
host: aarch64-apple-darwin
release: 1.89.0-nightly
LLVM version: 20.1.5

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-monomorphizationArea: MonomorphizationC-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions