Skip to content(if available)orjump to list(if available)

Crabtime: Zig’s Comptime in Rust

Crabtime: Zig’s Comptime in Rust

87 comments

·March 19, 2025

stared

I love these kinds of acknowledgements, as they not only show gratitude, but also give a glimpse into the collaborative, creative process:

> We would like to extend our heartfelt gratitude to the following individuals for their valuable contributions to this project:

> timonv – For discovering and suggesting the brilliant name for this crate. Read more about it here (https://www.reddit.com/r/rust/comments/1j42fgi/comment/mg6pw...).

> Polanas – For their invaluable assistance with testing, design, and insightful feedback that greatly improved the project.

> Your support and contributions have played a vital role in making this crate better—thank you!

bachmidt1007

[flagged]

weinzierl

I love the logo, it is brilliant.

Making Rust's macros easier is laudable. Purely from a user's perspective I find it especially annoying, that proc macros need their own crate, even if I understand the reasons for it. If I read Crabtime correctly it solves that problem, which is nice.

That being said Crabtime looks more like compile time eval on steroids to me than an analogon to Zig's comptime.

One (maybe the) distinguishing feature between comptime in Zig and Rust macros seems to me to be access to type information. In Zig you have it[1] in Rust you don't and that makes a big difference. It doesn't look like we will get that in Rust anytime soon and the projects that need it (e.g. cargo semver check) use dirty tricks (parsing RustDoc from the macro) to accomplish it. I did not see anything like that in Crabtime, but I might have missed it. At any rate, I'd expect compile time reflection for anything that claims to bring comptime to Rust.

[1] I think, but I am not a Zig expert, so please correct me if I am wrong.

pron

> One (maybe the) distinguishing feature between comptime in Zig and Rust macros seems to me to be access to type information. In Zig you have it[1] in Rust you don't and that makes a big difference.

There are other differences. First, comptime functions aren't syntactic macros. This makes them much easier to reason about and debug. You could think about them as if they were regular functions running at runtime in a partially-typed language with powerful reflection (their simplicity also means they're weaker than macros, but the point is that you can get very far with that, without taking on the difficulties associated with macros). Second, I think that comptime's uniqueness comes not from what it does in isolation, but that it makes other language features redundant, keeping the entire language simple. This means that with one simple yet just-powerful-enough feature you can do away with several other features.

The end result is that Zig is a very simple language with the expressivity of far more complicated languages. That on its own is not super unusual; in a way, JavaScript is like that, too. But Zig does it in a low-level language, and that's revolutionary. It is because of its simplicity that people compare Zig to C, but it's as expressive as C++ while also being safer than C++, let alone C.

Adding comptime to an already-complex language misses out on its greatest benefit.

GrantMoyer

Nit-pick: Javascript is not simple; I personally think it has among the most complex language semantics out of all commonly used languages.

HelloNurse

Javascript would be a simple hybrid of object oriented and functional principles if backward compatibility with old hacks and "robust" use for web page scripting didn't require a host of redundant features, syntactic bizarre special cases and and evil semantic choices: -- comments, iterating objects and arrays, the absurd equality operators and type conversions, and so on.

creata

> This makes them much easier to reason about and debug.

Can you give an example of something that's easier to reason about (e.g., an error that's easier to spot) with Zig's comptime than with macros?

> it makes other language features redundant

I'm guessing (so I might be wrong) that IDEs and users still need to be aware of the common idioms, so why does it matter whether or not those common idioms are implemented in the compiler or using comptime? (I'm not saying it doesn't matter, I'm wondering what benefits you have in mind.)

WhyNotHugo

> Can you give an example of something that's easier to reason about (e.g., an error that's easier to spot) with Zig's comptime than with macros?

Rust proc_macros takes a stream of tokens and return a stream of tokens. If your macro meant to return an instance of a specific type, it must output the correct tokens which create that instance via existing interfaces. There's some really ugly indirection in trying to understand what's going on.

This is always harder to reason about than Zig's equivalent, because in Zig you just return the thing that you want to return.

forks

What are some examples of other language features that comptime makes redundant?

dhruvrajvanshi

It's generic system for example, is built on top of comptime. A generic struct is just a function that takes a type as an argument and returns a struct.

``` fn Vec(comptime T: anytype) {

  return struct {

     // ...
  }
}

```

IMO having a first class generic type parameter syntax is better but this demonstrates OP's point.

pron

Generics, interfaces/traits/concepts, macros, conditional compilation, const functions/constexpr. These are four or five different features in C++ or Rust, some of which are quite complex, all expressible as one simple construct: comptime.

pjmlp

That is what I really like about the evolution of metaprogramming in C++.

While it started as a hack on how to use templates back in C++98, it has gotten quite usable nowadays in C++23, and the compile time reflection will make it even better.

All without having another language to learn about, as it happens with Rust macros, with its variations, or reliance on 3rd party crates (syn).

msk-lywenn

How is C++'s template metaprogramming not another language inside C++ today? AFAIK, the syntax and even general logic is still extremely different than regular C++

pjmlp

constexpr, consteval, if constexpr, requires, auto,... are quite regular C++.

Voultapher

[flagged]

nukem222

How on earth does zig resolve types before macros? Must be some ~~nuts~~ novel order of evaluation to get that behavior. How is this intended to function? Are there multiple layers of macros? Do you have to declare said level or is it derived? How do you use macros to define types or declare types of variables? Can you use said types in other macros?

pfg_

Zig doesn't have macros, it has functions which can be run at comptime. You can make a function that returns a type and call it from another function. All declarations are only analyzed when they are first used, and functions when called at comptime are memoized based on their arguments. The order of evaluation is really simple and predictable.

weinzierl

Just for completeness: Rust has functions which can be run at comptime as well. They are called const fn and Rust has them out of the box, no crate required. They are also true Rust and not macros with a separate syntax.

They are still not an adequate substitute for Zig's comptime feature. For one and in a sense they are much more limited than comptime functions in Zig but for another (and for better or worse) they also have much higher aspirations than Zig.

const fn must always be able to be run at compile time or run time and always produce bit-identical results. This is much harder than it looks at first glance because it must also uphold in a cross-compiling scenario where the compile time environment can be vastly different from the run time environment.

This requirement also forbids any kind of side effect, so Rust const fn are essentially pure functions and I've heard them called like that.

nukem222

> Zig doesn't have macros, it has functions which can be run at comptime

You raised my hopes and dashed them quite expertly, sir. Bravo!

wavemode

The answer is that zig doesn't have macros (i.e. syntactic transformations). Comptime functions in zig are just that - functions which run at compile time. They run after typechecking of existing types, but they are capable of creating new types. Types in Zig are just values. But they're values that don't exist at runtime.

deredede

Zig's comptime is not macros, it's staged programming / multi-stage programming.

Ygg2

Zig comptime is a C++ template done better. It also suffers from similar issues as C++ templates. You can't know function is comptime unless you put it in comptime and it passes.

See https://typesanitizer.com/blog/zig-generics.html

littlestymaar

This isn't a macro, it works as both macros and templates in C++, and regarding types it works the same way as templates in C++.

nindalf

I tried the library out and it worked pretty well for me.

I had previously written a declarative macro to generate benchmark functions [1]. It worked, but I didn't enjoy the process of getting it working. Nor did I feel confident about making changes to it.

When I rewrote it using crabtime I found the experience much better. I was mostly writing Rust code now, something I was familiar with. The code is much more readable and customisable [2]. For example, instead of having to pass in the names of the modules each time I added a new one, I simply read the files from disk at compile time.

To compare the two see what the code looks like in within the braces of paste!{} in the first one and crabtime::output!{} in the second one. The main difference is that I can construct the strings using Rust code and drop them in with a simple {{ str }}. With paste!, I don't know exactly what I did, but I kept messing around until it worked.

Or compare the two loops. In the first one we have `($($year:ident {$($day:ident),+ $(,)?}),+ $(,)?)` while with crabtime we have plain Rust code - `for (year, day) in years_and_days`. I find the latter more readable.

Overall I'm quite pleased with crabtime. Earlier I'd avoid Rust metaprogramming as much as possible, but now I'd be open to writing a macro if the situation called for it.

[1] - https://github.com/nindalf/advent/blob/13ff13/benches/benche...

[2] - https://github.com/nindalf/advent/blob/b72b98/benches/benche...

the__alchemist

Deos anyone have an example beyond the one on that page? I'm having a hard time understanding.

So, I'm interested in some metaprogramming right now. I'm setting up Vec3 SIMD types, and it requires a lot of repetition to manage the various variants: f32::Vec3x8, f64::Vec3x16 etc that are all similar internally. This could be handled using traditional macros, procedural macros, or something called "code gen", which I think is string manipulation of code. Could I use crabtime to do this instead? Should I?

CGamesPlay

Honestly, this long document is probably a better link than the crates.io page: https://docs.rs/crabtime/latest/crabtime/

> This could be handled using traditional macros, procedural macros, or something called "code gen", which I think is string manipulation of code. Could I use crabtime to do this instead? Should I?

You could, it seems. Crabtime supports both the procedural macros and "code gen" approaches you are talking about.

norman784

This looks nice, just yesterday I was trying to make my code more concise by using some macro_rules magic, but it was a bit more than what macro_rules can handle, so I ended up just writing the whole thing. I avoid whenever I can proc macros, I wrote my fair share of macros, but I hate them, you need to add most of the time 3 new dependencies, syn, quote and proc_macros2, that adds up to the compilation times.

This looks worth the playing with and see if they can solve my issue, one thing I avoid as much as possible is to add unnecessary dependencies, didn’t check how many dependencies this will add overall to the project.

lifthrasiir

It depends on proc-macro2, syn, quote, toml and rustc_version [1]. First three are legitimately expected for any complex enough procedural macros. Toml and rustc_version are apparently for automatic Cargo configuration and fairly harmless by their own. Their transitive dependencies are also not bad: unicode-ident (from proc-macro2), serde, serde_spanned, toml_datetime (from toml), and semver (from rustc_version).

[1] https://crates.io/crates/crabtime-internal/1.1.1/dependencie...

mplanchard

At first I was like wait this looks just like eval_macro, which I discovered a couple of weeks ago. Looks like it is just renamed! The new name is great, congrats on the improved branding :)

null

[deleted]

yc_are_pedos_0

[flagged]

thomspoon

You can say anything you want, yet you say nothing of importance

skinkestek

Exactly.

This person seems to have some serious issues :-)

vlovich123

Wow this is so neat. Has anyone had any experience with it / feedback? This looks so much nicer than existing macros.

nindalf

The author announced a previous version of this crate a couple of weeks ago and this new version two days ago. So there might not be that many users.

That said I did replace a declarative macro with it. Supposedly I wrote the declarative macro according to git blame but I only have a vague idea how it works. I replaced it with crabtime and I got something that I can understand and maintain.

Overall I’d say I’m very pleased with crabtime. Previously I would have avoided Rust metaprogramming as much as possible, but now I’d feel confident to use it where appropriate.

weinzierl

No experience. I agree that it looks nice and useful, but I don't think it is much like Zig's comptime.

Ygg2

It's neat, but I don't think it does the same as Zig's comptime. For one it doesn't have Zig's dynamic behavior, nor more practically compile time reflection.

Rust can't have Zig's comptime dynamic properties, same way Zig will never have Rust's compile time guarantees*.

You can't simultaneously have your dynamic cake and eat it at compile time.

* Theoretically you can have it, but it would require changing language to such extent it's not recognizable.

jpgvm

This is cursed in the most wonderful way, kudos.

Sharlin

It's what the word "blursed" was coined for.

airstrike

I need to use that more often...

null

[deleted]

jgalt212

Does anyone else find macros make it hard to grep a code base? This does seem like something semantic grep could solve, but I'm unaware of any semantic grep macros use cases.

mplanchard

This is why I generally avoid making new structs via macros, and why I personally dislike the popular error library snafu: breaking “go to definition” and codebase search really needs to be worth it IMO. It doesn’t feel as bad for proc macros that add methods for whatever reason for me, but having to use a type with an opaque definition hidden behind a macro really bugs me.

loeg

Moreso than calling an ordinary subroutine?