• 5 Posts
  • 10 Comments
Joined 5 years ago
cake
Cake day: May 31st, 2020

help-circle






  • Hmm, I haven’t really played around with const generics much, but I guess, you could maybe implement a custom InRange type, so you could use it like this:

    type Hour = InRange<0, 23>;
    
    let hour = Hour::new(17);
    

    That ::new() function would contain the assertions and could be const, but I don’t know, if that actually makes it execute at compile-time, when called in normal runtime code. Might be worth trying to implement it, just to see how it behaves.
    Was that an open question or did you have a solution in mind? 😅

    What would definitely work in Rust, though, is to implement a macro which checks the constraint and generates a compile_error!() when it’s wrong. Typically, you’d use a (function-like) proc_macro for this, but in this case, you could even have a macro_rules! macro with 24 success cases and then a catch-all error case.
    Well, and of course, it may also be fine (or even necessary) to check such numbers at runtime. For that, just a wrapper type with a ::new() function would work.


  • I don’t know much about Ada, but to my knowledge, its safety is difficult to compare with Rust.
    Ada has a type system that can express lots of details (like that an hour is in the range from 0 to 23), but then Rust prevents you from doing dumb things across threads (which might be part of the reason why a faster implementation was so quick to be implemented) and Rust has a more functional style, which also tends to avoid various bugs.

    And then, yeah, kind of similar thing for C/C++.
    If we just score the complexity and then compare numbers, I can see how you might arrive at the halfway mark. (Not knowing terribly much about C++ either, having so many legacy concepts feels incredibly daunting, so I’d put Rust rather at a third of the complexity points, but either is fair.)

    But yeah, on the other hand, I would not say that Rust is as if C and C++ had a baby with some footguns removed.
    C is a hardcore procedural language (aside from the ternary operator). I have to assume that C++ introduced some functional concepts at some point in its history, but Rust is much more oriented in that direction as a whole.

    I also believe your description of the borrow checker simply preventing RAII mistakes is a bit too simple. As I already mentioned, Rust also prevents you from doing dumb things across threads.
    It does so by the borrow checker checking that you only have one mutable reference at a time (“mutable reference” meaning the holder can modify the value behind the pointer). It also prevents having non-mutable references while a mutable reference is being passed around. If you actually need mutable access accross threads, it forces you to use a mutex or similar.

    And yeah, the borrow checker being such an integral aspect, I’d also argue that it has other effects. In particular, it really pushes you to make your program tree-shaped, so where data is initialized at the top of a (sub-)tree and then the data is only temporarily passed down into functions as references.
    IMHO that’s generally a good idea for making programs understable, but it’s a wild departure from the object-oriented world, for example, where everyone and everything just holds references to each other. (You can also do such references in Rust, if you need it, via Rc and Arc, but it’s not the first tool you reach for.)