What's the reason that fn in a trait cannot marked as a const

I'm curious why mark a function as const is not allowed in a trait, and the compiler said it is by design. Is there any technical reason?

In simple case, I can use an associated const, but how about complicated case? I didn't find anything called constant closure.

For example:

trait Vertex {
  const fn offset_and_type_id(&self, index: u64) -> (u64, u64);

Currently, I have to use macros to call offset_and_type_id on types which implement Vertex trait...

As far as I know, the question of how to represent a Fn's "constness" in the type system is still undecided and an active area of research (I believe the term is "effects system"). You'll notice that there is also no unsafe Fn(...) for this reason, either.

You may want to skim through the comments in the Fn traits tracking issue for more context:


In my case I use macros instead of traits to call const fn on types, do you think this is a good idea? Or are there any better workarounds?

Can you show me a cut-down example of what you mean?

In general, I prefer to avoid macros outside of generating boring "boilerplate" code (e.g. implementing some trait for all the primitive numeric types) because it hinders readability and makes the code harder to navigate using something like rust-analyzer's "go to definition" feature.

After using Rust for several years I also try to avoid advanced type-level trickery with traits unless I can avoid it. It's a powerful tool to have when you need it, and it's definitely helped save my bacon several times, but I've also seen how much harder a codebase becomes to use for less experienced rustaceans.

... It's an unpopular opinion, but sometimes a bit of copy/paste is better for readability/maintainability than the extra cognitive load you get when introducing complex generics or macros.


Hi, thanks for advising, would you please list some cases of "type-level trickery with traits"? I'm not very clear what kind of feature is a "type-level trickery with traits"...

I update my question for a cut-down example, BTW.

It's not a literal feature of the language, more a name I made up for the way you can compose traits, implementations, and where clauses to do complicated things.

For example, before const generics were implemented it was quite common for people to use something called "peano numbers" to do operations with numbers at compile time or associate a number with a type. See how the typenum crate it implements "type operators" and the generic_array crate.

Ages ago, Eddyb showed that Rust's type system is turing complete by writing a Brainfuck interpreter.

I've also seen/written code monsters that use way too many where-clauses and associated types in combination with other traits to express a complex set of requirements using the type system.

impl<'a, 'b, A, B, C> MyThing<'b, A>
  A: Iterator<Item = B>,
  B: AsRef<[C]>,
  &'a A: 'b,
  FooTrait<'a, (&'b A, C)>::AssociatedType: SomeMarker<B>

impl<A, B> SomeMarker<B> for Quux<A> where A: Into<B> + Clone { }

The ghost_cell crate is a nice example of how you can use lifetimes and variance to implement a RefCell-like type that is guaranteed to be correct at compile-time. I'll give it a free pass though, because while the theory for why it is sound is quite advanced, I feel the implementation is quite elegant and readable.

I've seen much better examples in real code where you use Rust generics in advanced ways to ensure certain guarantees/constraints, but I can't think of them at the moment. They're the sorts of things you normally try to forget :sweat_smile:


When seeing 'Turing complete' I suddenly remembered how C++ template comes to what is looks like today (start from the SFINAE and becomes today's hardly readable enable_if), hope Rust won't go the same way :rofl:

I'll investigate the resources you list, and thanks for the adverse and explanation, again.

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.