Skip to content

const generics return type with const expression mismatched types #69943

@cksac

Description

@cksac

I tried this code:

#![feature(const_generics)]
#![allow(incomplete_features)]
use std::fmt;

const fn max(a: usize, b: usize) -> usize {
    [a, b][(a < b) as usize]
}

pub struct Foo<const N: usize>;

impl<const N: usize> fmt::Debug for Foo<N> {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "Foo({:?})", N)
    }
}

// ok
fn bar_let<const N: usize, const M: usize>(a: Foo<N>, b: Foo<M>) {
    let a = Foo::<{ max(N, M) }>;
    println!("{:?}", a);
}

// // fail to compile
fn bar_ret<const N: usize, const M: usize>(a: Foo<N>, b: Foo<M>) -> Foo<{ max(N, M) }> {
    let a = Foo::<{ max(N, M) }>;
    println!("{:?}", a);
    a
}

fn main() {
    let a = Foo::<{ max(2, 3) }>;
    println!("{:?}", a);

    bar_let(Foo::<5>, Foo::<3>);

    let b = bar_ret(Foo::<5>, Foo::<3>);
}

I expected to see this happen:
compile success

Instead, this happened:

error[E0308]: mismatched types
  --> src/main.rs:27:5
   |
24 | fn bar_ret<const N: usize, const M: usize>(a: Foo<{ N }>, b: Foo<{ M }>) -> Foo<{ max(N, M) }> {
   |                                                                             ------------------ expected `Foo<{ max(N, M) }>` because of return type
...
27 |     a
   |     ^ expected `{ max(N, M) }`, found `{ max(N, M) }`
   |
   = note: expected struct `Foo<{ max(N, M) }>`
              found struct `Foo<{ max(N, M) }>`

Meta

rustc --version --verbose:

rustc 1.43.0-nightly (564758c4c 2020-03-08)
binary: rustc
commit-hash: 564758c4c329e89722454dd2fbb35f1ac0b8b47c
commit-date: 2020-03-08
host: x86_64-apple-darwin
release: 1.43.0-nightly
LLVM version: 9.0
Backtrace

<backtrace>

Activity

Patryk27

Patryk27 commented on Mar 12, 2020

@Patryk27
Contributor

This is correct according to the const generics' RFC:

Each const expression generates a new projection, which is inherently anonymous. It is not possible to unify two anonymous projections [...]

[...] const expressions do not unify with one another unless they are literally references to the same AST node. That means that one instance of N + 1 does not unify with another instance of N + 1 in a type.

cksac

cksac commented on Mar 12, 2020

@cksac
Author

thanks @Patryk27, seems it is an Unresolved questions according to the RFC. But the error message is a bit confusing.

   = note: expected struct `Foo<{ max(N, M) }>`
              found struct `Foo<{ max(N, M) }>`

Unification of abstract const expressions: This RFC performs the most minimal unification of abstract const expressions possible - it essentially doesn't unify them. Possibly this will be an unacceptable UX for stabilization and we will want to perform some more advanced unification before we stabilize this feature.

added
A-diagnosticsArea: Messages for errors, warnings, and lints
D-confusingDiagnostics: Confusing error or lint that should be reworked.
D-terseDiagnostics: An error or lint that doesn't give enough information about the problem at hand.
on Mar 13, 2020
slightlyoutofphase

slightlyoutofphase commented on Mar 13, 2020

@slightlyoutofphase
Contributor

FYI, this kind of thing compiles fine and does exactly what you expect it to do as long as you're not explicit about the generic constraints inside the function body, and let the return type be inferred from what you've already declared it as.

Here's a working playground link of your code, where the only change is just doing let a = Foo; inside of bar_ret instead of let a = Foo::<{ max(N, M) }>;.

cksac

cksac commented on Mar 13, 2020

@cksac
Author

@slightlyoutofphase thanks, that works. but method call will fail to compiles

fn bar_ret_2<const N: usize, const M: usize>(a: Foo<N>, b: Foo<M>) -> Foo<{ max(N, M) }> {
    bar_ret(a, b)
}
slightlyoutofphase

slightlyoutofphase commented on Mar 13, 2020

@slightlyoutofphase
Contributor

Yeah, I guess that kind of direct "passthrough" where both functions have exactly the same generic constraints and the second one returns the result of the first doesn't quite work yet.

I feel like that's not a huge issue probably, though.

The sort of syntax that does compile, i.e. the let b = bar_ret(Foo::<5>, Foo::<3>); is IMO likely the more common way of using const generics.

I'd say to me at least, also, having an actual "call" (so to speak) to the max const fn literally as a generic parameter seems like probably not the most expected way of going about it. I'd personally view max as more something you'd call with literals to determine the generic N of a Foo, like:

type MyFoo = Foo<{max(9, 18)}>;

None of this really matters though, just my two cents after having written a bunch of code using const generics, haha.

slightlyoutofphase

slightlyoutofphase commented on Mar 13, 2020

@slightlyoutofphase
Contributor

Actually, wait, I was wrong, there's a way to make this work so that you're even able to call bar_ret from bar_ret_2 exactly like in your last comment. You just need to declare a type alias like this:

type MaxFoo<const N: usize, const M: usize> = Foo<{ max(N, M) }>;

Here's a working playground link that expands on your code a bit and uses the MaxFoo alias (and also adds a bar_ret_3 that in turn calls bar_ret_2 just to show it works).

I guess the reason it works with an alias probably has something to do with forcing the max call to be evaluated earlier than it would be with the max directly in the return type. Not sure though.

Patryk27

Patryk27 commented on Mar 13, 2020

@Patryk27
Contributor

Not sure though.

Using a type alias is exactly what the original RFC proposes to solve this issue.

slightlyoutofphase

slightlyoutofphase commented on Mar 13, 2020

@slightlyoutofphase
Contributor

Is it a purely technical limitation currently that it doesn't work with the "direct" use of max in the return type, then?

I can't imagine it's intentional when it seems like most people would basically expect the direct use to amount to being completely equivalent to the alias in practice.

11 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-const-genericsArea: const generics (parameters and arguments)A-diagnosticsArea: Messages for errors, warnings, and lintsC-bugCategory: This is a bug.D-confusingDiagnostics: Confusing error or lint that should be reworked.D-terseDiagnostics: An error or lint that doesn't give enough information about the problem at hand.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.requires-nightlyThis issue requires a nightly compiler in some way.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @cksac@crlf0710@estebank@jonas-schievink@Patryk27

        Issue actions

          const generics return type with const expression mismatched types · Issue #69943 · rust-lang/rust