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) }>`
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>
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 + 1does not unify with another instance ofN + 1in a type.
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.
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) }>;.
@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)
}
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.
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.
Not sure though.
Using a type alias is exactly what the original RFC proposes to solve this issue.
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.
I also found it very hard or not possible to use type alias as workaround in some cases, example
related issue #62058
@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.
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.