[HN Gopher] Crabtime: Zig's Comptime in Rust
___________________________________________________________________
Crabtime: Zig's Comptime in Rust
Author : klaussilveira
Score : 378 points
Date : 2025-03-19 18:44 UTC (3 days ago)
(HTM) web link (crates.io)
(TXT) w3m dump (crates.io)
| weinzierl wrote:
| 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.
| nukem222 wrote:
| 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_ wrote:
| 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.
| nukem222 wrote:
| > 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!
| gliptic wrote:
| You're probably underestimating what you can do with
| these.
| weinzierl wrote:
| 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.
| kzrdude wrote:
| Rust has compile time environment variables and const fns
| can parse them. It's one very nice and easy way to
| experiment with or configure rust code at compile time,
| and should be explored more.
| Zambyte wrote:
| Reading environment variables contradicts my mental model
| of pure functions. Are they just not pure functions then?
| Or pure-ish?
| kzrdude wrote:
| The functions are pure but they take an input from a
| built-in (looks like a macro) that reads the environment
| variable at compile time.
|
| Also, you only compile once, so how could you tell the
| difference? You could say - if it was using const fn that
| it's a "templated" function that depends on compile time
| settings.
| Zambyte wrote:
| I see, I guess if you can't set environment variables
| during build time that makes it pure-ish enough.
| vlovich123 wrote:
| You can set environment variables in your build.rs / the
| user sets it like A=b cargo build.
| dwattttt wrote:
| There's two lookups that can occur, distinguishing them
| makes it clearer.
|
| Looking up the value of an environment variable at
| runtime is not a const operation, and produces an error
| if you try to do it in a const fn.
|
| Looking up the value of an environment variable during
| compile time _can_ be done in a const context, but it'll
| only happen once. The environment should be considered an
| input to a const fn, and that makes it "pure".
|
| EDIT: These two operations can both be done in non-const
| functions too, they're different functions (well, one's a
| macro).
| johnisgood wrote:
| So if Rust has Zig's comptime feature, what is this
| crate? How does it differ, what does it add?
| IshKebab wrote:
| Rust _doesn 't_ have Zig's comptime feature. Rust's const
| fn's are normal functions that are _capable_ of running
| at compile time. It 's an optional optimisation; it
| doesn't exist any additional semantic capabilities
| because they also need to be able to run at runtime.
|
| Zig's comptime functions _only_ run at compile time, so
| they can do extra things - in particular manipulating
| types - that you can 't do if your function needs to run
| at runtime. (Don't mention dependent types.)
| johnisgood wrote:
| Okay, so this crate adds Zig's actual comptime?
| weinzierl wrote:
| No, it doesn't because it is based on Rust macros which
| are strictly less capable than Zig comptime in a crucial
| way (compile time reflection).
|
| Neither Rust macros nor const fn are 100% what Zig
| comptime is but they have other properties that Zig
| comptime lacks. Apples and oranges.
| zozbot234 wrote:
| Note that dependently-typed code also effectively "runs
| at compile-time", it's inherent to that programming
| model. You can "extract" an ordinary program from
| dependently-typed code which you can then compile to a
| binary and run as usual, but then that program will not
| feature dependent types in their full generality.
| bsder wrote:
| > Zig's comptime functions only run at compile time, so
| they can do extra things - in particular manipulating
| types - that you can't do if your function needs to run
| at runtime.
|
| Careful, I'm not sure this is true. I haven't found a Zig
| comptime function that doesn't also work just as well at
| runtime function.
|
| This is, in fact, the primary characteristic that makes
| Zig comptime easier to reason about than any "macro"
| system. If something is wrong in my comptime function, I
| can normally make a small adjustment to force it to be a
| runtime function that I can step through and probe and
| debug.
|
| It's sort of a unification of compile time and run time
| semantics and it is _long_ overdue. The late John Shutt
| 's Scheme-alike Kernel
| (https://web.cs.wpi.edu/~jshutt/kernel.html) sort of
| approached this as did old-school Tcl.
| IshKebab wrote:
| Yeah I don't really understand why Rust copied that from
| C++, where constexpr functions only _might_ run at
| compile time.
|
| C++ ended up having to add consteval and constinit which
| really are compile-time.
| kibwen wrote:
| It's the other way around. Rust has always had contexts
| that are guaranteed to be compile-time (const and static
| items), and gradually added the ability to run some
| subset of the language at compile-time (const fn)
| specifically to accommodate const/static items (e.g. to
| replace the old lazy_static with the modern LazyLock),
| and naturally also allows these functions to run at
| runtime if you want (and in what would otherwise be a
| runtime context, you can enforce compile-time evaluation
| with a const block).
| tialaramex wrote:
| Huh? What is it you think Rust copied here? I agree that
| the choice in C++ is essentially worthless, so that in
| practice you can write functions which are definitely
| never executed at compile time and aren't constant in any
| sense, label them constexpr and that compiles anyway. It
| just becomes yet more noise C++ programmers learn to type
| by reflex to get the correct behaviour from their
| compiler, joining explicit.
|
| But in Rust that's not what you're getting. Rust's const
| fn is none of the options C++ decided it needed, Rust
| says _if_ the parameters are themselves constants _then_
| we promise we can evaluate this at compile time and if
| appropriate we will -- this means we can use Rust 's
| const fn where we'd use C++ consteval, but the function
| can also be called at runtime with variable parameters -
| and we can use Rust's const where we'd use C++ constinit,
| calling these const fn with constant parameters.
|
| Because Rust is more explicit about safety of course, we
| can often get away with claiming some value is "constant"
| in C++ despite actually figuring out what it is at
| runtime, and Rust isn't OK with that, for example in my
| code const OVERSIZE: u32 =
| SIG_BITS.next_power_of_two() << 1;
|
| We can just calculate what power of two is bigger than
| SIG_BITS and shift it left at compile time. But...
| pub(super) static SHORT_80: LazyLock<Rational> =
| LazyLock::new(|| Rational::fraction(1, 80).unwrap());
|
| The Rational type is a big rational, it owns heap
| allocations so we'll just make one once, at runtime, and
| then re-use it whenever we need this particular fraction
| (it's for calculating natural logarithms of arbitrary
| computable real numbers).
| IshKebab wrote:
| > Rust says if the parameters are themselves constants
| then we promise we can evaluate this at compile time and
| if appropriate we will
|
| Well exactly. "if appropriate". So like C++'s
| `constexpr`, Rust doesn't make any guarantees about
| compile-time evaluation.
|
| Zig's `comptime` _must_ be evaluated at compile time.
| kibwen wrote:
| _> Zig 's `comptime` must be evaluated at compile time._
|
| Yes, and the equivalent in Rust is any constant context,
| such as a const item, or a const block inside of a non-
| const function. Anything in a constant context is
| guaranteed to run at compile-time.
| tialaramex wrote:
| If we want a constant context, we can say so. Because
| Rust is expression oriented we can write for example a
| loop (though if you're unfamiliar with Rust it may not be
| clear why a _for_ loop can 't work yet, other loops are
| fine) and wrap the whole expression in a const block and
| that'll be evaluated at compile time. For example:
| let a = const { let mut x: u64 = 0;
| let mut k = 5; loop { if k ==
| 0 { break x; }
| k -= 1; x += 2; x *= x;
| } };
| tyilo wrote:
| Floats are not guaranteed to be bit-identical at compile
| time and run time in Rust.
| weinzierl wrote:
| Last time I checked the float functions that have no bit-
| identical results (mostly transcendental functions) were
| missing from Rust's const fn for exactly that reason.
| kibwen wrote:
| It's not quite as bad as it sounds, because the only
| difference is that the representation of NaN (the sign
| and the payload bits) isn't guaranteed to be stable. If
| you're not relying on any specific representation of NaN,
| then floating-point math in const fn is identical, and
| observed differences would be considered a soundness bug
| in the Rust compiler.
| weinzierl wrote:
| I had to look this up because it is a while that I tried
| to use floating point math in a const fn and it seems
| that the differences you described have been decided to
| be acceptable.
|
| Looks like floating point math in const fn is coming.
| Here is the respective tracking issue:
| https://github.com/rust-lang/rust/issues/128288
| kibwen wrote:
| It's actually already here, it stabilized last year in
| 1.82: https://blog.rust-
| lang.org/2024/10/17/Rust-1.82.0.html#float...
| weinzierl wrote:
| Oh, nice. Sometime I find it really hard to track the
| status of new Rust features. For example the tracking
| issue I linked is still open with "Stabilize" missing.
| throwawaymaths wrote:
| what happens if you compile on a system that has a
| different precision than the system you run on? like
| suppose you compile on a 64 bit system targetting 32 bit
| embedded with an fp accelerator or a 16 bit system with
| softfloat?
| kibwen wrote:
| I'm not personally familiar with the implementation, but
| Rust's const fn is evaluated using an interpreter called
| MIRI with its own softfloat implementation, and therefore
| isn't limited by the precision of the host platform. The
| act of cross-compilation shouldn't pose a problem, and
| would be a soundness issue in the compiler if it did.
| throwawaymaths wrote:
| no the soundness of the compiler is at risk because the
| _target_ has limitations, not the host.
| codedokode wrote:
| They are not guaranteed to be precise anyway.
| WhyNotHugo wrote:
| What Rust is missing is reflection and the ability to
| define types and functions via code. Zig's comptime is
| often used for this: to generate code (for example, a
| serialiser for a type) or to generate types (generics
| being a typical thing, but lots of other usages are
| viable).
| vlovich123 wrote:
| You can define types and functions via code via macros.
| For example, [1] which creates a new sibling type and
| injects a ::builder method into your type.
|
| And you can add reflection [2]. So if you can add what
| you need via crates, is the language actually missing it
| or is it just not as ergonomic / performant as it needs
| to be or is it an education problem?
|
| [1] https://docs.rs/typed-builder/latest/typed_builder/
|
| [2] https://docs.rs/reflect/latest/reflect/
| wavemode wrote:
| 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 wrote:
| Zig's comptime is not macros, it's staged programming /
| multi-stage programming.
| Ygg2 wrote:
| 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
| pjmlp wrote:
| C++98 templates done better, we are past that now in
| C++23.
|
| Also D and Circle, done it before Zig.
| andrepd wrote:
| >we are past that now
|
| Jesus, we very much aren't. The only real step
| improvement was in C++11 which added constexpr which (in
| the following years) gradually obviated the need for
| C++98 style TMP. But there really hasn't been much of a
| generational improvement since (OK, concepts I guess),
| and it remains cumbersome and error prone and difficult
| to debug.
| pjmlp wrote:
| If I compare C++98 template metaprogramming with tag
| dispatch, ADL, and SFINAE, with what C++23 offers, it is
| already a complete different world, even if there are
| still warts to improve.
| samatman wrote:
| This blog post is disqualified from any serious
| discussion, because it doesn't know the distinction
| between templates, which Zig's comptime constructs are
| not, and partial evaluation with reified types, which
| Zig's comptime constructs are.
|
| It's not possible to make a positive contribution after a
| mistake that basic.
|
| Here's an example of someone getting the design space
| correct, and therefore contributing to the discussion in
| a positive way. He doesn't end up liking Zig, for reasons
| I disagree with, but he does completely evade being not-
| even-wrong, which is table stakes.
|
| https://hirrolot.github.io/posts/why-static-languages-
| suffer...
| Ygg2 wrote:
| > This blog post is disqualified from any serious
| discussion, because it doesn't know the distinction
| between templates
|
| Just because a blog doesn't go full type theory doesn't
| disqualify it from drawing conclusions based on
| experience and limitations incurred during actual use.
|
| Something can be very well typed but still suck to use.
|
| Intution doesn't need to be based on formal
| understanding. See Table of elements. Created by grouping
| elements by behavior, it turned out to be based on
| electron orbital configuration.
| samatman wrote:
| The central claim is that Zig's use of comptime is
| similar enough to templates to conflate them. That's
| simply incorrect. There's no value in trying to extract
| information from something which makes such a basic
| mistake as that, it doesn't contribute to a discussion,
| it distracts that discussion down a blind alley.
| Ygg2 wrote:
| I think it's insightful to some extent. The problems
| encountered in C++ templates apply to Zig's comptime as
| well. And their solution seem to be along same lines,
| i.e. add constraints.
|
| Edit: on re-reading the author doesn't understand why
| negative traits in Rust are a problem (not is basic
| boolean operation). I think they are abstracting too much
| and saying cows should be roughly spherical and water
| should roughly be a superconductor.
| littlestymaar wrote:
| 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++.
| SkiFire13 wrote:
| I'll leave this here, try guessing what this prints:
| const std = @import("std"); const myType =
| struct { const info = @typeInfo(myType);
| const before = info.Struct.decls.len; pub
| usingnamespace (if (before == 0) struct { pub fn baz() void
| {} } else struct {}); const after1 =
| info.Struct.decls.len; const after2 =
| @typeInfo(myType).Struct.decls.len; };
| pub fn main() void { std.debug.print("Hello, {}
| {} {}!\n", .{ myType.before, myType.after1, myType.after2 });
| }
| throwawaymaths wrote:
| well i suppose this is a good part of the reason why
| usingnamespace is likely to go the way of the dodo, though
| if i had to guess:
|
| hello: 4, 5, 5
| pjmlp wrote:
| 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 wrote:
| 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 wrote:
| constexpr, consteval, if constexpr, requires, auto,... are
| quite regular C++.
| codedokode wrote:
| The problem with C++ metaprogramming is that it is pain to
| read and understand, unless it is your daily job.
| pron wrote:
| > 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.
| forks wrote:
| What are some examples of other language features that
| comptime makes redundant?
| dhruvrajvanshi wrote:
| 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.
| Wumpnot wrote:
| It just looks like C++ templates with a slightly
| different syntax ..
| azakai wrote:
| Exactly, the point is that C++ added templates as a huge
| new language feature, while in Zig it is just one of the
| things that is immediately possible thanks to comptime.
| throwawaymaths wrote:
| well, not quite since you can pass non-type things of
| generally _any_ level of data type complexity (as long as
| it 's comptime-valid, which only excludes certain types
| of mutation), and do stuff with them that you couldnt in
| c++. fn MyType(T: type, comptime tag:
| [] u8, comptime count: usize) type { const
| capitalized = some_module.capitalize(tag);
| return struct{ fn name() []const u8 {
| return capitalized; } array:
| [count]T, }; }
|
| for example
| Wumpnot wrote:
| That example looks easy enough to replicate in C++ with
| consteval + template, basically the same except a few
| minor syntax changes.
| Maxatar wrote:
| You absolutely can't do that in C++ with consteval +
| template. C++ would need support for reflection to do
| that, and maybe it will get it in 10 years, maybe not,
| but as of today this would not be possible.
|
| Furthermore, the original argument wasn't about whether
| something can or can't be done in C++, it was that this
| one feature in Zig subsumes what would require a
| multitude of features from C++, such as consteval,
| templates, SFINAE, type traits, so on so forth...
|
| Instead of having all these disparate features all of
| which work in subtly different ways, you have one single
| feature that unifies all of this functionality together.
| steveklabnik wrote:
| You're not wrong in general here, but C++ is going to get
| the core of reflection in C++26. I'm not sure enough of
| the details to know if it supports doing this, however.
|
| Rust on the other hand... that might be ten years.
| throwawaymaths wrote:
| i didn't use reflection in this example, but note that
| consteval shouldn't be able to do this because I _mutate_
| the string; it 's not const at comptime.
| SkiFire13 wrote:
| Nit: comptime does not replace a proper generic system
| (i.e. a polymorphic type system), but acts more like a
| templating system (like the one in C++).
| pron wrote:
| 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.
| kobebrookskC3 wrote:
| how does zig express a trait like Send https://doc.rust-
| lang.org/std/marker/trait.Send.html which ensures that
| values can safely be moved to another thread, for example
| when spawning a thread? https://doc.rust-
| lang.org/std/thread/fn.spawn.html
| throwawaymaths wrote:
| if you want that you will need a proof checker or
| something.
| pcwalton wrote:
| How do you typecheck generics, with type inference, with
| comptime?
|
| Or, more generally, address all the issues raised in [1].
| You're saying that comptime can fully replicate all the
| features that a proper generics system has, which is
| plainly false.
|
| [1]: https://typesanitizer.com/blog/zig-generics.html
| creata wrote:
| I don't think pron was saying that Zig has a feature-by-
| feature match for everything that Rust's generics can do.
| I think his point is that comptime can handle what the
| target audience of Zig wants from generics. In that
| regard, I don't think the criticisms there are that big a
| deal.
| throwawaymaths wrote:
| 1. if you wish, you absolutely can check for "extra
| constraints" on a passed type (or even an anytype
| parameter) using comptime reflection and the
| @comptimeError builtin.
|
| 2. if you want to restrict the use of a function to
| comptime (why you would want to is beyond me) it is
| possible to do with @inComptime builtin.
|
| the only tricky bit is that your function _could_ try to
| call a function inaccessible to you because it 's
| transitively restricted and you'd have a hard time
| noticing that from the code but it's not possible for
| that code to be compiled (barring errors by the zig team)
| so its more of an annoyance than a problem.
| pron wrote:
| I would say these are more differences than issues, and
| that some of those presented as more fundamental ones are
| actually quite small. Suppose that instead of `fn foo
| (comptime T : type, ...) { typecheck(T); ...}` Zig
| introduced just a tiny bit of new syntax to allow you to
| write something like `fn foo (comptime T : typecheck(T),
| ...) { ... }` -- i.e. the type constraints would be part
| of the signature -- would you then say it had generics
| rather than templates? Personally, I have not made up my
| mind on whether or not such an addition would be very
| valuable, but even if it is, it can be done later. That
| small addition would address most "issues" in the
| article, which I would say are more about IDE support
| than anything else. But even without it, what you want to
| know is known at compile time, and the article admits
| that the compilation errors are already better than those
| you get with C++ templates (I would say much better).
|
| Now, I'm not saying that Zig's choices always dominate
| and that all languages would be better off with its
| approach; far from it. I am saying that it introduces a
| novel tradeoff that is especially compelling in cases
| where not only generics but also macros, conditional
| compilation, and constexprs are otherwise required. In a
| language like Java these extra features are not required,
| and so Zig-style comptime would not simplify the language
| nearly as much.
|
| But even in cases where all these features are needed, I
| don't think everyone would take Zig's choices over C++'s
| or Rust's, or vice-versa. To those, like me, for whom
| language complexity is the biggest problem with C++ or
| Ada (I used Ada in the nineties), Zig is a revolutionary
| step forward. I don't think any low-level language has
| ever been this simple while also being this expressive.
| SkiFire13 wrote:
| Comptime can only properly express half of them:
|
| - generics: comptime can implement some kind of
| polymorphism, but not at the type level. In other words
| it implements a templating system, not a polymorphic type
| system;
|
| - interfaces/traits/concepts: comptime implements none of
| that, it is plain duck typing, just like "old" C++
| templates. In fact C++ introduced concepts to improve its
| situation with templates, while Zig is still behind on
| that front!
|
| - macros: comptime solves some of the usecases where
| macros are used, but it cannot produce arbitrary tokens
| and hence cannot fully replace macros.
|
| I do agree that it can neatly replace conditional
| compilation and const functions/const expr, but let's not
| make it seem like comptime solves everything in the
| world.
| throwawaymaths wrote:
| in practice aside from interfaces the only thing you
| can't do at comptime is to generically attach
| declarations (member functions, consts) to a container
| type (the best you can do is to do it on a case by case
| basis).
|
| you could probably cobble together an interface system
| with @comptimeError, but because of the time order of
| compilation stages, a failed method call will trigger the
| compiler error _before_ your custom interface code,
| making it effectively useless for the 90% of cases you
| care about.
|
| if I'm not mistaken in principle a small change to the
| compiler could amend this situation
| creata wrote:
| > 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 wrote:
| > 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.
| NobodyNada wrote:
| How does it work if I wanted to construct a type (and
| maybe a set of helper types. some related functions,
| etc.), rather than an instance of a type?
|
| If I just wanted to construct an instance of a specific
| type at compile time in Rust, I'd probably be using a
| const fn instead of a macro.
| cgh wrote:
| You return the type directly. You can then declare things
| to be of this type. Eg, from the Zig docs, here's how to
| construct a generic List type (note the comptime
| declaration of the generic parameter): fn
| List(comptime T: type) type { return struct {
| items: []T, len: usize, };
| } // The generic List data structure can be
| instantiated by passing in a type: var buffer:
| [10]i32 = undefined; var list = List(i32){
| .items = &buffer, .len = 0, };
| samatman wrote:
| You can't reason about macros, that's not how they work.
|
| You can read their definition, you can expand them, but
| there's no way to look at a macro call and reason about it,
| it can do anything at all. In C you don't even know what is
| and isn't a macro, so Rust has a modest edge in that
| respect.
|
| Zig just doesn't have this problem to begin with.
| creata wrote:
| Reading a macro's definition and reasoning about its
| effect is... reasoning. It's not the same as reasoning
| about something using its inherent limitations, which is
| the kind of reasoning that I think you're referring to,
| but it's still reasoning.
| samatman wrote:
| Ok, sure, we can reason about anything. We could reason
| about machine code, if we had the time and inclination.
|
| I barely participate in Hacker News anymore because it
| seems to have collectively lost the ability to extract
| meaning from words, unless an exhausting and totally
| excessive amount of attention is put into satisfying a
| misplaced sense of precision. There's no intellectual
| charity left and it sucks.
| GrantMoyer wrote:
| Nit-pick: Javascript is not simple; I personally think it has
| among the most complex language semantics out of all commonly
| used languages.
| HelloNurse wrote:
| 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.
| efnx wrote:
| Prototypal inheritance can be pretty awkward and
| complicated. It's evident in that few young JS devs ever
| use it, and many don't even know about it!
| Lerc wrote:
| Can you give an example outside of the weirdness caused by
| automaticy converting types? (Or 'with', which is kind-of
| in purgatory now)
|
| I have considered a non backwards compatible JavaScript
| descendant to clean things up. It would be interesting to
| hear what you consider to be problems.
| GrantMoyer wrote:
| Off the top of my head:
|
| - There's both undefined and null
|
| - There's three ways to declare variable
|
| - typeof vs instanceof
|
| - for in vs for of
|
| - the way `this` works
|
| - semantics of functions called with the wrong number of
| args
|
| There's far more that I can't immediately recall.
| throwawaymaths wrote:
| prototypes vs class
| panzi wrote:
| Empty slots in arrays are weird. >>>
| let xs = [,'foo'] <<< undefined >>> xs
| <<< Array [ <1 empty slot>, "foo" ] >>>
| xs.map((x, index) => index) <<< Array [ <1 empty
| slot>, 1 ] >>> for (let x of xs) console.log(x);
| undefined foo <<< undefined >>>
| for (let x in xs) console.log(x); 1 <<<
| undefined >>> xs.length <<< 2
| >>> delete xs[1] <<< true >>> xs.length
| <<< 2 >>> xs <<< Array [ <2 empty slots>
| ]
|
| Empty slots are skipped in map() and for-in, but not in
| for-of and the new array from map() will have the same
| empty slots. delete will change a slot to empty, it won't
| change the length of the array.
|
| Still, _much_ saner than PHP arrays.
| codedokode wrote:
| Javacripts has lot of low-level functions like [[Get]],
| [[Call]], [[ToPrimitive]] which can be redefined. If you
| believe that JS is easy, do you remember how these low-
| level functions work? Also, JS has prototypes.
| vlovich123 wrote:
| Wow this is so neat. Has anyone had any experience with it /
| feedback? This looks so much nicer than existing macros.
| weinzierl wrote:
| No experience. I agree that it looks nice and useful, but I
| don't think it is much like Zig's comptime.
| nindalf wrote:
| 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.
| Ygg2 wrote:
| 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.
| norman784 wrote:
| 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 wrote:
| 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...
| nindalf wrote:
| 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...
| jpgvm wrote:
| This is cursed in the most wonderful way, kudos.
| Sharlin wrote:
| It's what the word "blursed" was coined for.
| airstrike wrote:
| I need to use that more often...
| stared wrote:
| 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/rus
| t/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!
| the__alchemist wrote:
| 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 wrote:
| 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.
| codedokode wrote:
| For simply copy-pasting code you could start with using
| simplest traditional macros. No matter what approach you choose
| your code will be pain to read and understand (maybe we need to
| have "show generated code" button in our IDEs).
| conaclos wrote:
| Actually we have a command to do exactly what you want:
| `expand macro`. Crabtime claims to have the same thing.
| jgalt212 wrote:
| 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 wrote:
| 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.
| Nullabillity wrote:
| Snafu works fine with at least rust-analyzer's gotodef
| (though go-to-references is indeed broken by it :/).
| loeg wrote:
| Moreso than calling an ordinary subroutine?
| dymk wrote:
| The code demo in the crabtime readme is actually a good
| example of something that is now hard to grep. Let's say you
| see a usage of `Position1`, and you want to find where and
| how it's defined - well, there is no `enum Position1` in the
| codebase, because the identifier is concatenated from two
| separate parts. You lose out on some IDE niceties as well -
| can't command-click on the definition site to find usages,
| because there is no definition site (available to you, at
| least).
| codedokode wrote:
| The problem is not macros, it is concatenation of identifiers.
| I stumbled upon this a lot when working with CSS preprocessors
| which allow you to write code like this:
| .user { &--profile { color: red; } }
|
| Now, searching for "user--profile" CSS class becomes imposible.
| Despite this, SASS and similar preprocessor seem to be popular
| and used almost everywhere. Well, I never had high expectations
| of front-end developers, so I am not very disappointed.
|
| So I think as long as you don't break identifiers, the code
| should be searchable. But, your IDE will probably not able to
| help you with auto-complete and navigation.
| jgalt212 wrote:
| > The problem is not macros, it is concatenation of
| identifiers.
|
| Yes, that's also a problem. As is MySQLCursorDict Class. Now
| you have to grep every column name every table your code base
| accesses.
| mplanchard wrote:
| 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 :)
| KolmogorovComp wrote:
| A better link https://docs.rs/crabtime/1.1.1/crabtime/
| dymk wrote:
| This looks cool, but how it impacts project compile times? They
| talk about how caching works for multiple invocations of the same
| macro with different arguments. It would be nice to have some
| approximate numbers for how long it takes to create, compile, and
| execute one of its generated projects.
| cyber1 wrote:
| No, this is not Zig comptime at all. Zig's comptime work is on
| another level, and it's amazing.
| metaltyphoon wrote:
| Instead of just saying its not. Explain what's so much
| different here.
| jedisct1 wrote:
| It's nothing like Zig's comptime.
| codedokode wrote:
| The problem with macros in Rust is that they have full access to
| your computer. This is literally an invitation for exploitation.
| I think we will see the attacks based on this vulnerability once
| Rust becomes more popular.
| cmrx64 wrote:
| There's already a runtime for sandboxing macros with wasm:
| https://github.com/dtolnay/watt
| codedokode wrote:
| So you need to use hacks, like compiling code into a web-
| browser language and messing with config files instead of
| having security out-of-box?
|
| But thank you for letting me learn something useful.
| cmrx64 wrote:
| It's a demonstration. wasm is a portable ISA more than a
| "language". Surely it makes sense to build things
| incrementally, in layers? https://internals.rust-
| lang.org/t/pre-rfc-sandboxed-determin...
|
| But go off, king.
| jkelleyrtp wrote:
| `make myfile.mk` -> pwned
|
| I do share the sentiment - and complain about this frequently -
| but any environment with build scripts can wreck your computer.
| Encrypt what you can, I guess, but software engineering is an
| extremely dangerous job wrt security.
| hypeatei wrote:
| Do other languages have a security model for this? I've always
| assumed that building arbitrary code could execute something in
| most languages.
|
| I think using something like the pledge syscall from OpenBSD in
| the compiler could be useful. That way, it's controlled at the
| process level which things can be accessed on the system.
| codedokode wrote:
| C macros and gcc do not allow to run arbitrary code during
| compilation.
| cchance wrote:
| I don't know if i'm too dumb i never understood what comptime
| gives, i get what macros are for but how does something like
| crabtime improve things ?
|
| Does this basically allow us to write normal rust code instead of
| procmacros, with even fewer constraints?
| tdhz77 wrote:
| Moments like this realize I don't understand programming
| fundamentals. Imposter syndrome sits in and I realize that my
| lack of formal education is costing me.
___________________________________________________________________
(page generated 2025-03-22 23:01 UTC)