What do you want to do with CTFE?

One of the most wanted post-1.0 features is some kind of CTFE. However, CTFE is a huge feature-space. What do you want to do with it?

I currently know of:

  1. Generic integer parameters, without arithmetic. I think this is planned quickly after 1.0.
impl<n: uint> MyTrait for [T; n] {
    fn foo(&self) {
        for i in 0..n::value() {
        println!("{}", self[i]);
    }
}
  1. C++-style dynamic type functions, i.e. type-level printf. Rust is statically typed at all levels and is not going to have these.

  2. Static functions in constant initializers. Planned for an unknown date.

  3. Static functions in static array sizes. i.e. struct A { d: [u8; required_size()] }. I don't know of any plans for this but this is acceptable.

Are there other things I am missing?

1 Like

>uint

(damn you discourse, im trying to be cheeky, dont say i need 20 characters)

to be serious though, im pretty sure that things like your example will be very useful with CTFE....

That was intentionally uint, we don't really have sized compile-time integers :frowning: (currently they are all u64).

I was mostly trying to figure out the design space, and what people want. I mean, people always say they want C++-style templates while what they really ask for is either eDSL-s (which are well-served by compiler extensions) or sizing an array by static info, or sometimes even just to genericize over an integer.

Vaguely, I want to be able to do everything that can be done with CTFE with some future version of 'syntax extensions' (they wouldn't be just for syntax anymore) instead, as an alternative. The same way macro_rules! is technically Turing complete but people aren't much tempted to abuse it, CTFE could thus remain a useful but limited feature without people trying to cram everything they might want to do at compile time into it.

Could you be more specific? Different languages, and different people, seem to have different definitons for what CTFE means. Anyway, I think that people don't abuse macro_rules! because you can do the more complicated things in a syntax extension.

Sorry, the posts I write while tired have a habit of being unclear. What you said is exactly what I meant: just as syntax extensions allow macro_rules! to be a simple feature, by providing an alternative that's clunkier but highly general and powerful for the truly demanding use cases, a variant of syntax extensions that can work directly with the type system could do the same for a native CTFE feature. For example, basic arithmetic may be doable with some concept of "constexpr functions", as proposed in the PR that was discussed in the recently posted meeting notes, but then what if you want to call a function that uses the heap, or reads from the filesystem (at compile time)?

I'm not quite sure what the API boundaries for such plugins should be, but they should be able to do things like

  • iterate over every struct field to generate debugging, serialization, and tracing code - like you can already do with syntax extensions, but with the ability to properly inspect the types of those fields rather than just having the names.
  • kill with fire any use cases where type-level unary numerals make sense today.
  • add LINQ to the language, like in C# (with, say, the ability to run a 'query optimizer' at compile time rather than just blindly applying lambdas).
    edit: - implement async functions ;p
1 Like

I've noted CTFE pretty much boils down to syntax extensions or its replacement.

Could you elaborate? I've never seen type-level unary numerals that I recall off. Was this some kind of Brainfuck implementation using types?

What do either of these have to do with CTFE? Can't they be implemented regardless of CTFE? I can kinda see LINQ CTFE, where the compile time some kind of plan is fetched from DB and applies some transformation on code, but I'm not 100% sure what else is there in C# LINQ.

Constructors, constructors, constructors. Currently it's impossible to initialize a const or static Duration for example, because not all of its fields are public.

The only workaround for this (employed by Cell) is to make all fields pub and #[doc(hidden)]

Is anyone interested in CTFE at ABI-generation time (i.e. struct Foo { a: [u8; my_static_fn()] } or some variant)? This interacts in a slightly ugly way with constexpr size_of.

That's also important for generating byte arrays as long as some struct.

The part of Linq that would involve CTFE is expression trees. C# lets you build expressions and then compile them using the JIT, much like Lisp macros. Doing that at compile time essentially boils down to nicer compiler plugins.

That could also be used to implement C#-style async/await and yield, which involve compile-time transformations of functions into state machines using keywords placed in the code.

Please, no. No. NOOOO.

Macros are stupidly, and we are already seeing a proliferation of non-trivial pointless macros. With this we'll soon see a proliferation of pointless syntax extensions. And while I can barely live live with clearly-marked macros (in the worst case I can expand them), this kind of extensions will make debugging simply impossible.

Please, stop considering expressiveness alone. ALWAYS consider it along with the maintenance burden on people who'd have to support unknown code.

That might be required for associated constants to work well, too.

I would like generic integer parameters with arithmetic.

In rust it is reasonably easy to define numerical newtypes for restricting operations done on them to those that make at least elementary sense (e.g. you can add a timestamp and a duration, but adding two timestamps won't compile), but proper dimensional analysis like Boost.Units requires having an array of signed integers in the type signifying exponent for each dimension. Array can be done with a linked list, but the values need to be added when multiplying and subtracted when dividing.

I would like to use CTFE for optimizing memory-mapped IO code in microcontrollers and enforcing constraints at compile-time. The Rust Zinc project currently does this with syntax extensions, but I find it extremely verbose and hard to understand.

Rather, I've employed CTFE to great effect for this purpose in D

Examples:

  • Attempting to write to a readonly register or bitfield, it will generate a compiler-time error
  • Bitfields that lie on a byte boundary can be optimized at compile-time to a single, atomic read/write rather than a read-modify-write reducing code size and increasing speed.
  • If reading/writing a single bit, the address can be inspected at compile-time and optimized to use the ARM's bit-banding region, again turning a read-modify-write into a single read or write.
  • When writing to multiple bitfields within a single register, the bitmasks and shifts can all be computed at compile time to a single read-modify-write rather than doing the bit-twiddling at run time

The same can be accomplished using Rust's syntax extensions and macros as the Zinc project has shown, but CTFE could greatly simplify the technique.

Some kind of static if feature would also be nice.

1 Like

For bit level stuff, I found myself using CTFE's a lot. Like calculating minimum power of 2, using a constant in a type (your point #1), other things I can't remember. I see it as necessary because otherwise, some things are impossible or wayyy too difficult to express otherwise.

CTFE is awesome because it allows for many crazy optimizations for people needing those without extending the scope of a language much (for the users - for implementers it's a whole different story).

Usage of CTFE:

  • Dlang has a compile-time regexp implementation using CTFE here: std.regex - D Programming Language

  • it's also used in dlang to implement custom type literals - again a feature which doesn't require extending language at all

  • it can be used to generate fixed size structures for fast access, I know a location-grid implementation for a game implemented using C++98 template meta-madness - with CTFE this can be replaced with straightforward code.

On a final note - there's a certain elegance in being able to use most of the language in a context where only compile time constant could be used.

Indeed. Too much macros and magic will make the code unmaintainable. To be quite honest, the Deref feels too magical for me, syntax plugins aside.

What can be done to limit and denote Magic™ of CTFE? One nice thing about syntax extensions is that they don't look like regular code.

Compile-time function execution (i.e. constexpr function in C++, or RFC #911) is different from compile-time code generation (i.e. the mixin() expression in D).

D's power partly comes from the ability to generate strings that can be pasted directly into code while compiling. It is mixin() which is magical, CTFE is pretty harmless and much less powerful on its own.

For instance you could parse an arbitrary complex string and create a complicated struct instance (e.g. to reproduce the regex! macro) (not available in RFC #911 yet, control structure is not supported), but you cannot create a new item (e.g. bitfield!, defining coroutines, etc.) with CTFE alone.

2 Likes

Regexes can be implemented at compile time in Rust also, CTFE not needed.

(To thread:)

Technically, Rusts type system is turing complete, so we already do have CTFE, its just very verbose...