Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

const generics return type with const expression mismatched types #69943

Closed
cksac opened this issue Mar 12, 2020 · 13 comments
Closed

const generics return type with const expression mismatched types #69943

cksac opened this issue Mar 12, 2020 · 13 comments
Labels
A-const-generics Area: const generics (parameters and arguments) A-diagnostics Area: Messages for errors, warnings, and lints C-bug Category: This is a bug. D-confusing Diagnostics: Confusing error or lint that should be reworked. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. requires-nightly This issue requires a nightly compiler in some way. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@cksac
Copy link

cksac commented Mar 12, 2020

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>

@cksac cksac added the C-bug Category: This is a bug. label Mar 12, 2020
@Patryk27
Copy link
Contributor

Patryk27 commented Mar 12, 2020

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
Copy link
Author

cksac commented Mar 12, 2020

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.

@jonas-schievink jonas-schievink added A-const-generics Area: const generics (parameters and arguments) F-const_generics `#![feature(const_generics)]` requires-nightly This issue requires a nightly compiler in some way. labels Mar 12, 2020
@estebank estebank added A-diagnostics Area: Messages for errors, warnings, and lints D-confusing Diagnostics: Confusing error or lint that should be reworked. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. labels Mar 13, 2020
@slightlyoutofphase
Copy link
Contributor

slightlyoutofphase commented Mar 13, 2020

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
Copy link
Author

cksac commented Mar 13, 2020

@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
Copy link
Contributor

slightlyoutofphase commented Mar 13, 2020

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
Copy link
Contributor

slightlyoutofphase commented Mar 13, 2020

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
Copy link
Contributor

Not sure though.

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

@slightlyoutofphase
Copy link
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.

@cksac
Copy link
Author

cksac commented Apr 1, 2020

I also found it very hard or not possible to use type alias as workaround in some cases, example

@cksac
Copy link
Author

cksac commented Apr 1, 2020

related issue #62058

@crlf0710 crlf0710 added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Jun 11, 2020
@lcnr
Copy link
Contributor

lcnr commented Sep 14, 2020

@varkor

Why do you consider this blocking? Unifying generic constants is not necessary for min_const_generics, as that only deals with ConstKind::Param or concrete values, both of which unify just fine.

@varkor
Copy link
Member

varkor commented Sep 14, 2020

I marked this one more as a note to myself to check that we couldn't get diagnostics like this still, where it suggests two things are different that look literally the same. But, as you say, it shouldn't be an issue for min_const_generics.

@lcnr
Copy link
Contributor

lcnr commented Jun 28, 2022

the unification for generic constants has now been implemented under the generic_const_exprs feature. bar_ret now compiles and we already have similar tests.

@lcnr lcnr closed this as completed Jun 28, 2022
@lcnr lcnr removed the F-const_generics `#![feature(const_generics)]` label Jun 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-const-generics Area: const generics (parameters and arguments) A-diagnostics Area: Messages for errors, warnings, and lints C-bug Category: This is a bug. D-confusing Diagnostics: Confusing error or lint that should be reworked. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. requires-nightly This issue requires a nightly compiler in some way. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

8 participants