6 ms·
If D manages to implement sound borrowing checking this will be huge! Brings it a step closer to matching Rust's feature list. - Zero-cost abstractions: Yes!
by logicchains 7y ago
If D manages to implement sound borrowing checking this will be huge! Brings it a step closer to matching Rust's feature list.
- Zero-cost abstractions: Yes!
- Move semantics: Yes!
- Guaranteed memory safety: Yes! (in code marked with the appropriate annotations)
- Threads without data races: Yes!
- Trait-based generics: No!
- Pattern matching: No! (Although like C++, in can be done as a library: https://code.dlang.org/packages/sumtype https://code.dlang.org/packages/sumtype)
- Type inference: Yes! (Plus unlike Rust, you can also mark function return types auto, and let them be inferred too).
- Minimal runtime: Yes! (the GC could be disabled if using exclusively borrow-checked code)
- Efficient C bindings: Yes!
D also brings some features of its own to the table that Rust doesn't yet match.
- Higher-kinded types (template template params): it's possible to implement Haskell-style monads etc. in D, if for some reason you felt the need.
- Pure functions: these allow you to statically guarantee that a piece of code doesn't perform any IO and is a completely deterministic function of its inputs. This makes reasoning about a large codebase much easier, compared to Rust where there's no way to statically guarantee a function isn't doing any IO.
- Fast compile times: the reference DMD compiler is almost as fast as the Go compiler, at least for code that doesn't do a bunch of compile-time calculation.
- Compile-time compute: Rust now has constfn, but that still only supports a limited subset of the language. D allows almost the entire language to be used at compile time.
- Variadic templates: Something C++ users might miss when coming to Rust, they allow for the creation of things like tensors of arbitrary dimension (myTensor<6, 3, 2, 5, 6, 20>) on which operations are checked at compile time to ensure sizes are compatible (so it's a compiler error if you try to multiply two tensors of the wrong size or wrong number of dimensions).
- coder543 7y agoThe problem with D is that it is so non-uniform. People talk about the betterC mode, but not nearly all D code written can run in this mode. Now people will talk about this ownership mode, but very little code will really use it. To become an expert in D or C++, you really need to understand what feels like a dozen different languages... and that’s not what I’m looking for in a single language. D’s concept of ownership (for now) doesn’t seem to have any plan to support partially moved structs, which are a major ergonomic feature. D has organically grown just about every interesting feature ever conceived, but I would much rather use a purpose-built language, which is what Rust feels like. I don’t want a C++ that’s better at being C++ than C++ is. I really enjoy how consistent and expressive Rust is. I also appreciate how intentional Go is about being Go. A lot of people enjoy C++, and some people enjoy D. I enjoyed C++ back when Rust wasn’t an option... then something better came along. D and C++ try to be everything to everyone. I like D better than C++... but I have seen no compelling reason to trade Rust for D. Variadic templates are much better expressed by either tuples or const generics. A fixed length array of a constant generic length is the Rustic solution to tensor-like problems, and it is being rigorously developed. Arbitrary compile time computation should be separated from the body of the program. If you need to download files or read the file tree at compile time, that should happen in a build script. Rust provides build.rs for that. Precomputing values without I/O is conceptually just an advanced kind of compiler optimization, and that’s why Rust is basically seeking to make const fns only pure functions. The fast compile times are a double edged sword, since those binaries are noticeably slower than Rust binaries, from what I’ve seen. I hope that Rust will one day have a much faster compiler for development builds. I also think every sign is showing that Rust is starting to get some real traction, so I don’t think I’m alone in these opinions... but they are mostly just that: opinions. You’re welcome to your own.
- Scarbutt 7y agoIf you don't mind, I'm curious about what "partially move structs" are? Structs have always feel like a chore to me, complicating things more when compared to maps in dynamic langs. Maybe Rust has something for dealing with the "explosion of types" issue and dealing with partial information in structs that I don't know about. Skimming through the rust book looks like Option<T> is the solution to missing data in structs.
- coder543 7y agoPartially moved just means that the compiler tracks which fields have been “moved out” of the struct and prevents you from either accessing those fields or attempting to reuse the partially moved struct as if it were still a whole struct, since it is no longer whole... it has given up some of its members, conceptually. In the article linked by this discussion, the author specifically mentions how fields can’t be moved out in D’s implementation. Option is the correct approach for dealing with information that might be missing, but that’s not really related to partially moved structs. Structs document what is available in a given value, to both the compiler and to you. If that’s not convincing, then I’m not going to try more here... it’s off topic. But I will say you can use maps in Rust or other statically typed languages. They’re just not meant to be an alternative to well-defined structs.
- Scarbutt 7y agoSure, my intention was not to get in a dynamic vs static lang argument, when seeing "partial moved structs" and "ergonomics" in the same sentence my mind jump to think that there is something Rust that can make it feel closer to a dynamic lang regarding types ;) Thanks.
- didibus 7y agoCan Rust now handle partial move semantics on structs? I remember reading it couldn't do it before, and there were threads about how you should make small structs because of that, or that you should not use methods, because they take ownership of the full struct and not just the fields that they actually use, etc.
- pcwalton 7y ago> - Higher-kinded types (template template params): it's possible to implement Haskell-style monads etc. in D, if for some reason you felt the need. Because the templates aren't typed at all. I'm not sure you can call untyped templates "Haskell-style".
- logicchains 7y agoWhile the templates are technically untyped (in the sense that there are no type-classes or traits in D), you can use template constraints to achieve something similar: https://dlang.org/concepts.html https://dlang.org/concepts.html. Essentially you'd write a compile time isMonad predicate that only returned true if certain functions were defined for a type. Even without that, the key thing is it's still possible to implement monads in the C++ style (https://stackoverflow.com/a/2565191/2553416 https://stackoverflow.com/a/2565191/2553416), whereas it's impossible to write code to do the same thing in current Rust. It just gives much worse compiler errors if you use it wrong compared to if the language had built in type-classes / traits.
- pcwalton 7y agoTemplate constraints aren't the same thing as typed generics. The key issue is that typed generics influence the typechecker. For example, you can write "let x = Default::default(); f(x);" and the compiler can determine the type of x from the type of f. Also, there is the "higher" crate for higher-kinded types if you need them, so it is possible to write a monad typeclass in Rust. It's not exactly something you'd want to use, though.
- logicchains 7y ago>For example, you can write "let x = Default::default(); f(x);" and the compiler can determine the type of x from the type of f. That would be less helpful in C++/D because their compilers don't do Hindley-Milner style bidirectional inference, so even if they had typed generics they wouldn't necessarily be able to do that inference. But I suppose that's an argument in favour of Rust, stronger type inference. >It's not exactly something you'd want to use, though. Ergonomic HKT can make writing various kinds of utilities easier. E.g. imagine I want to write a function Singleton that takes a collection C (vector, list, stream...) and an item of type T, and returns C<T>, a collection containing a single instance of that type. I can write that in Haskell, D or C++, but not in Rust. Or perhaps more usefully, a function Fill, that returns a container C containing n items of type T. For Rust it seems I'd need to write a separate implementation of Fill for each container type, I couldn't write one parameterised over any C?
- tus88 7y ago> Guaranteed memory safety: Yes! (in code marked with the appropriate annotations) Really? So if a thread passes a pointer to an object to another thread which frees it, the sending thread can no longer reference it? Genuine qn.
- Crinus 7y agoAccording to the linked article, by "passing a pointer" the sending thread relinquishes ownership of the object and, yes, cannot access it anymore after the point where it was sent. Of course this assumes all code in these threads use the ownership+borrowing mode.
- piano 7y ago> - Pure functions: these allow you to statically guarantee that a piece of code doesn't perform any IO and is a completely deterministic function of its inputs. Is this actually true? As far as I know D's pure isn't the same as eg. Haskell pure, it only prevents modifications of global state, but you can still touch data referenced by arguments. I haven't done much D though so I'm not sure what the semantics really are...
- WalterBright 7y agoIf the parameters to a pure function are marked immutable, then you cannot change data referenced by those arguments. It's functionally pure. Ironically, sometimes people complain that the purity checks are too stringent :-) One thing interesting about pure functions in D is the purity restrictions can be relaxed by using a `debug` prefix: pure int test(int i) { printf("%d\n", i); // error, printf isn't pure debug printf("%d\n", i); // ok return i; } This makes it very, very handy to debug your functional code.