9 ms·
For me, there's a headline draw, which is the borrow checker. Really great. But apart from that, Rust is basically a bag of sensible choices. Big and small stu
by lordnacho 2y ago
For me, there's a headline draw, which is the borrow checker. Really great.
But apart from that, Rust is basically a bag of sensible choices. Big and small stuff:
- Match needs to be exhaustive. When you add something to the enum you were matching, it chokes. This is good.
- Move by default. If you came from c++, I think this makes a lot of sense. If you have a new language, don't bring the baggage.
- Easy way to use libraries. For now it hasn't splintered into several ways to build yet, I think most people still use cargo. But cargo also seems to work nicely, and it means you don't spend a couple of days learning cmake.
- Better error handling. There's a few large firms that don't use exceptions in c++. New language with no legacy? Use the Ok/Error/Some/None thing.
- Immutable by default. It's better to have everything locked down and have to explicitly allow mutation than just have everything mutable. You pay every time you forget to write mut, but that's pretty minor.
- Testing is part of the code, doesn't seem tacked on like it does in c++.
- jvanderbot 2y agoAdd to this: trait system vs deep OOP. Really nice macro system. First class serde. First class sync/send Derives!
- pjmlp 2y ago"Applying Traits to the Smalltalk Collection Classes", 2003 https://rmod-files.lille.inria.fr/Team/Texts/Papers/Blac03a-OOSPLA03-TraitsHierarchy.pdf https://rmod-files.lille.inria.fr/Team/Texts/Papers/Blac03a-... Traits as CS concept, are part of OOP paradigm.
- jvanderbot 2y agoThere are shades of OOP, and while you're technically correct I think the meaning of my post is clear.
- sshine 2y agoTraits as CS concept, are part of FP paradigm. Reverse Uno!
- pjmlp 2y agoYes, and someone called Simon Peyton Jones happens to have a talk on how Haskell type classes and classical OOP interfaces interrelate.
- sshine 2y agoYes, and someone called Gabriella Gonzales happens to have a blog post on how objects are like comonads: https://www.haskellforall.com/2013/02/you-could-have-invented-comonads.html https://www.haskellforall.com/2013/02/you-could-have-invente... And someone called Samuel the Bloggy Badger happens to have another blog posts on how comonads are really more like neighborhoods: https://gelisam.blogspot.com/2013/07/comonads-are-neighbourhoods-not-objects.html https://gelisam.blogspot.com/2013/07/comonads-are-neighbourh... ...so it might all just be a scam!
- kccqzy 2y agoThe traits concept mentioned in your link looks very different from Rust traits. It describes something more akin to Java interfaces.
- pjmlp 2y agoJava interfaces are based on Objective-C protocols. The only big difference is how implementation is mapped into the trait specification.
- kccqzy 2y agoAnd that's the problem isn't it? Rust traits are based on GHC type classes, not at all from either Java or Objective-C or Smalltalk.
- pjmlp 2y agoThankfully this fellow Simon Peyton Jones has a talk about how they map into OOP paradigm. "Classes, Jim, But Not as We Know Them — Type Classes in Haskell: What, Why, and Whither" https://www.microsoft.com/en-us/research/publication/classes-jim-not-know-2009/ https://www.microsoft.com/en-us/research/publication/classes... "Adventure with Types in Haskell" https://www.youtube.com/watch?v=6COvD8oynmI https://www.youtube.com/watch?v=6COvD8oynmI https://www.youtube.com/watch?v=brE_dyedGm0 https://www.youtube.com/watch?v=brE_dyedGm0 On the first lecture he discusses how Haskell relates to OOP in regards of subtyping and generic polymorphism and how although different on the surface they share those CS concepts in their own ways.
- kccqzy 2y agoNo. Did you read the contents of the links you shared? The name of the slides in your first link is "Classes, Jim, but not as we know them". And let me quote from the slides in your first link: From slide 40: > So the links to intensional polymorphism are closer than the links to OOP. From the first bullet of slide 43: > No problem with multiple constraints > f :: (Num a, Show a) => a -> ... From the second bullet: > Existing types can retroactively be made instances of new type classes (e.g. introduce new Wibble class, make existing types an instance of it): > class Wibble a where > wib :: a -> Bool > instance Wibble Int where > wib n = n+1 From slide 46: > In Haskell you must anticipate the need to act on arguments of various type > f :: Tree -> Int > vs > f’ :: Treelike a => a -> Int > (in OO you can retroactively sub-class Tree) From slide 50: > In Java (ish): > inc :: Numable -> Numable > from any sub-type of Numable to any super-type of Numable > In Haskell: > inc :: Num a => a -> a > Result has precisely same type as argument I appreciate you sharing informative links even though they prove you wrong. I haven't seen this set of slides before but I find it a very good concise explanation of why Haskell classes are not traditional OOP classes or interfaces.
- rmgk 2y agoTraits in Rust are more a variant of Haskell typeclasses than of Smalltalk traits. The whole FP vs OOP distinction does make little sense these days, as it has mostly been shown that each concept from the one can neatly fit within the other and vice versa.
- Asraelite 2y ago> First class serde. What do you mean? `Serialize` and `Deserialize` are not part of std.
- tialaramex 2y agoIt's true, they're not part of the standard library. Nevertheless, it is conventional to provide implementations for things you reasonably expect your users might want to serialize and deserialize. Standard guidance includes telling you to name a feature flag (if you want one for this) serde and not something else so as to reduce extra work for your users. Because Rust's package ecosystem is more robust it's less anxious about the strict line between things everybody must have (in the standard library) and things most people want (maybe or maybe not in the standard library). In C++ there's a powerful urge to land everything you might need in the stdlib, so that it's available. For example the FreeBSD base system includes C++. They're not keen on adding to their base system, so for example they seem disinclined to take Rust, but when each C++ ISO standard bolts in whatever new random nonsense well that's part of C++ so it's in the base system for free. Weird data structure a game dev wants? An entire linear algebra system from Fortran? Comprehensive SI unit systems? It's not up to the FreeBSD gatekeepers, a WG21 vote gets all of those huge requirements into FreeBSD anyway.
- jvanderbot 2y agoThis was a conscious decision by Rust folks. Let the language and std libraries be small enough to target anything - and let well-established crates (most written/started by the rust folks) fill in functionality. The main language provided the baseline interfaces, in some cases (see async), but not the machinery (e.g., async runtimes).
- loeg 2y agoFWIW C++ in FreeBSD is a little contentious. The overall system build time is dominated by Clang, with the rest of FreeBSD "a wart on the side." In base, the C++ compiler was pretty much only used for devd (something vaguely like Linux' udev), and devd is written in a pre-C++11 dialect -- no new features. Using more of it isn't exactly encouraged; it's not allowed in the kernel. There are two significant barriers to Rust in FreeBSD base -- first, cultural: it's just a bunch of greybeards opposed to anything and everything new; and second, technical: Rust just doesn't (or didn't) have compiler backends for the same subset of platforms FreeBSD does (or did). (This situation is improving as FreeBSD finally drops official support for obsolete SPARC, 32-bit ARM, MIPS, and 32-bit PowerPC platforms, but obviously cultural barriers remain.)
- jimbob45 2y agoMove by default. If you came from c++, I think this makes a lot of sense. If you have a new language, don't bring the baggage. Came from C++ and this is my least favorite part of the language ergonomics.
- eddd-ddde 2y agoWhy? It makes you use smart pointers correctly from the start. Any big c++ codebase would do this anyway, except it isn't as error prone.
- tialaramex 2y agoIt actually doesn't come from C++ and what C++ has is worse, the history is interesting. The move assignment semantic you see in Rust was also retrospectively termed "destructive" move because after the assignment A = B not only is the value from B now in A - that value is gone from B, B was in some sense "destroyed". If we write code which does A = B and then print(B) it won't compile! B is gone now. Programmers actually really like that, it feels natural (with appropriate compiler support of course) and it doesn't have unexpected horrors to be uncovered. In C++ they couldn't make that work (without destroying compatibility with existing C++ 98 code) so they invented their own C++ 11 "move" which is this more fundamental move plus making a new hollow object to go in B. This new hollow object allows the normal lifecycle of C++ 98 objects to happen as before - B goes out of scope, it gets destroyed. So in C++ A = B; print(B) works - but it's not defined to do anything useful, you get some ready to clean up object, if B was a string maybe it's the empty string, if B was a remote file server then... maybe it's an "empty" remote file server? That's awkward. It's worth understanding that the nicer Rust move isn't a novelty, or something people had no idea they wanted when C++ 11 was standardized, the "destructive" move already existed and was known to be a good idea - but C++ couldn't figure out a way to deliver it.
- repelsteeltje 2y agoI think the main motivation for adding move semantics to c++11 was performance. Ie. Eliminate superfluous copy constructors when passing a std::string temporary into a function. Std::move, std::forward are neat, though somewhat cumbersome compared to Rust. C++ scope, lifetime plus the fact that std::move doesn't actually move are real footguns. There have been attempts to add destructive moves (Circle) but it's a long way from Rust's ergonomics. I concur with op that default move semantic is where rust shines.
- synergy20 2y agoagree on all these though i ended up using golang for faster development
- Animats 2y ago> Rust is basically a bag of sensible choices. Mostly yes. In C/C++, the defaults are usually in the less safe direction for historical reasons.
- tialaramex 2y agoIt's not about less safe, the C++ defaults are usually just wrong. It's so well known that Phil Nash had to make clear whether he was giving the same talk about how all the defaults are wrong at CppCon or a different talk, otherwise who knows. For some cases you can make an argument that the right default would have been safer. For mutability, for avoiding deductions, these are both sometimes footguns. But in other cases the right default isn't so much safer as just plain better, the single argument constructors should default to explicit for example, all the functions which qualify as constexpr might as well be constexpr by default, there's no benefit remaining for the contrary. My favourite wrong default is the memory ordering. The default memory ordering in C++ is Sequentially Consistent. This default doesn't seem obviously wrong, what would have been better? Surely we don't want Relaxed? And we can't always mean Release, or Acquire, and in some cases the combination Acquire-Release means nothing, so that's bad too. Thus, how can Sequentially Consistent be the wrong default? Easy - having a default was wrong. All the options were a mistake, the moment the committee voted they'd already fucked up.
- n144q 2y ago> Match needs to be exhaustive. When I see people mention C++ with MISRA rules, I just think -- why do we need all these extra rules, often checked by a separate static analysis tool and enforced manually (that comes down to audit/compliance requirement), when they make perfect sense and could be done by the compiler? Missing switch cases happens often when an enum value is modified to include one extra entry and people don't update all code that uses it. Making it mandatory at compiler level is an obvious choice.
- jpc0 2y ago-Wswitch ¶ Warn whenever a switch statement has an index of enumerated type and lacks a case for one or more of the named codes of that enumeration. (The presence of a default label prevents this warning.) case labels that do not correspond to enumerators also provoke warnings when this option is used, unless the enumeration is marked with the flag_enum attribute. This warning is enabled by -Wall. <https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wswitch https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#inde...> The compiler can do that... And it's included in -Wall. It's not on by default but is effectively on in any codebase where anyone cares... Please don't argue about "but I don't need to add a flag in Rust" it's not rust, there's reasons the standard committee finds valid for why and honestly your welcome to implement your own compiler that turns it on by default just like the rust compiler which has no standard because "the compiler is the standard".
- tialaramex 2y agoMISRA won't be OK with that. MISRA requires that you explicitly write the default reject. So -Wswitch doesn't get it done even though I agree that if C had (which it did not) standardized this requirement that would get you what you need. C also lacks Rust's non_exhaustive trait. If the person making a published Goose type says it's non-exhaustive then in their code nothing changes, all their code needs to account for all the values of type Goose as before - but everybody else using that type must accept that the author said it's non-exhaustive, so they cannot account for all values of this type except by writing a default handler. So e.g if I publish an AmericanPublicHoliday type when Rust 1.0 ships in 2015, and I mark it non-exhaustive since by definition new holidays may be added, you can't write code to just handle each of the holidays separately, you must have a default handler. When I add Juneteenth to the type, your code is fine, that's a holiday you must handle with your default handler, which you were obliged to write. On the other hand IPAddr, the IP address, is an ordinary exhaustive type, if you handle both IPv6Addr and IPv4Addr you've got a complete handling of IPAddr.
- tsimionescu 2y ago> Better error handling. There's a few large firms that don't use exceptions in c++. New language with no legacy? Use the Ok/Error/Some/None thing. I think this is still very much a debatable point. There are disadvantages to exceptions, mostly around code size and performance. But they are still the only error handling mechanism that anyone has found that defaults to adding enough context to errors to actually be useful (except of course in C++, because C++ doesn't like having useful constructs). Rust error handling tends towards not adding any kind of context whatsoever to errors - if you use the default error mechanisms and no extra libraries. That is, if you have a call stack three functions deep that uses `?` for error handling, at the top level you'll only get an error value, you'll have no idea where the value originated from, or any other information about the execution path. This can be disastrous for actually debugging hard to reproduce errors.
- mr_00ff00 2y agoI feel like your last point is the exact issue with exceptions, not rust’s errors. Exceptions are like having “?” on every single line.
- tsimionescu 2y agoWhen an exception happens, you get a stack trace somewhere in your logs (unless you do something really weird). That doesn't always include all the information you'd like (for example, if the error happened in a loop, you don't get info about the loop variable). In contrast, unless you manually add context to the error (or use a library that does something like this for you, overriding the default ? behavior), you won't get any information about where an error occurred at all. Sure, with exceptions, you don't know statically where an exception might happen. But at runtime, you do get the exact information. So, if the error is hard to reproduce, you still have information about where exactly it occurred in those rare occasions where it happened.
- tialaramex 2y ago> When an exception happens, you get a stack trace somewhere in your logs OK, so, if I write the canonical modern C++ Hello World, execute it against an environment where the "standard output" doesn't exist, where does this stack trace get recorded? Maybe it depends on the compiler and standard library implementation somehow? My impression is that in reality C++ just ignores the problem and carries on, so actually there was no stack trace, no logging, it just didn't work and too bad. Unsurprisingly people tasked with making things work prefer a language which doesn't do that.
- dataflow 2y ago> Move by default. If you came from c++, I think this makes a lot of sense. > Immutable by default. In C++, these two fight each other. You can't (for the most part) move from something that's immutable. How does Rust handle this? I assume it drops immutability upon the move, and that doesn't affect optimizations because the variable is unused thereafter?
- lightingthedark 2y agoRust moves aren't quite the same as C++ moves, you can think of them more like a memcpy where the destructor (if there is one) doesn't get run on the original location. This means you can move an immutable object, the object itself doesn't have to do anything to be moved.
- NobodyNada 2y agoIn Rust, when you move out of a variable, that variable is now effectively out-of-scope; trying to access it will result in a compile error. Mutability in Rust is an attribute of a location; not a value, so you can indeed move a value from an immutable location into a mutable one, thus "dropping immutability". (But you can only move out of a location that you have exclusive access to -- you can't move out of an & reference, for example -- so the effect is purely local.)
- dataflow 2y agoYeah that sounds about like what I expected. Thanks!
- remram 2y agoYou can't refer to any old location so there is no observable mutation. For example you can't move if a reference exists.
- baq 2y agoNot 100% sure but sounds like you want Pin<>?
- belter 2y ago> There's a few large firms that don't use exceptions in c++ Google: https://google.github.io/styleguide/cppguide.html#Exceptions https://google.github.io/styleguide/cppguide.html#Exceptions
- dataflow 2y agoJust make sure you read the whole darn thing: > Given that Google's existing code is not exception-tolerant, the costs of using exceptions are somewhat greater than the costs in a new project. > ...Things would probably be different if we had to do it all over again from scratch. It's quite ironic to cite the Google C++ Style Guide as somehow supporting the case against exceptions. It's saying the opposite: we would probably use exceptions, but it's too late now, and we can't. Somehow people miss this...
- nox101 2y agoexceptions in C++ are a foot gun. Even the top C++ gurus/leaders know this and are trying to find some new solution https://www.youtube.com/watch?v=os7cqJ5qlzo https://www.youtube.com/watch?v=os7cqJ5qlzo
- dataflow 2y agoThanks for the 1-hour video. Could you link to the timestamp of the strongest argument(s) you see in the video that are relevant in the current discussion (i.e. the existing error models we're talking about in Rust and C++, rather than a hypothetical future one)? Just from a quick glance: I see he's talking about things like stack overflows and std::bad_alloc. In a discussion like this, those two are probably the worst examples of exceptions. They're the most severe exceptions, and the one the fewest people care to actually catch, and the ones that error codes are possibly the worst at handling anyway. (Do you really want an error returned from push_back?) The most common stuff is I/O errors, permission errors, format errors, etc. which aren't well represented by resource exhaustion at all, much less memory exhaustion. P.S. W.r.t. "the top C++ gurus/leaders" - Herb is certainly talented, but I should note that the folks who wrote Google's style guide are... not amateurs. They have been involved in the language development and standardization process too. And they're just as well aware of the benefits and footguns as anyone.
- nox101 2y ago> Easy way to use libraries This is both a blessing and a curse. Seeing the rust docs require 561 crates makes it clear that rust/cargo is headed down the same path as node/npm Downloaded 561 crates (50.7 MB) in 5.21s (largest was `libsqlite3-sys` at 5.1 MB)
- spullara 2y agothis is good actually
- zdragnar 2y agoAlmost all of the things that were wrong with NPM were self inflicted. No name spacing packages by default, allowing packages to be deleted / removed without approval, specifying install ranges and poor lock file implementation and so on. There's an argument to be made that there are too many packages from too many authors to trust everything. I don't find the argument to be too convincing, because we can play what-if games all day long, and if you don't want to use them, you get to write your own.
- skydhash 2y agoThe issue is micro-packages. Instead of a few layers between the os and your code, you find yourself with a wide dependency tree, with so many projects that it’s impossible to audit.
- dwattttt 2y agoAn alternative of "now everyone who uses a linked list has their own mostly-the-same, but-just-different-enough" list.c and list.h files that need separate auditing (if you care) isn't better.
- skydhash 2y agoIf list.c is part of the project, it’s easier because you don’t have to hunt down every dependency’s repository. It’s much easier to audit and trust 5 projects/orgs, than 50 different entities.
- OJFord 2y ago> Testing is part of the code, doesn't seem tacked on like it does in c++. Or most languages! Many could easily imitate it too. I'd love a pytest mode or similar framework for python that looked for doc tests and has a 'ModTest' or something class.
- prmph 2y agoSo when are we going to get a proper application (not systems) programming language with all these nice things about Rust?
- mananaysiempre 2y ago> Match needs to be exhaustive. When you add something to the enum you were matching, it chokes. This is good. There’s a reason why ML and Haskell compilers generally have that as a warning by default and not an error: when you need a pipeline of small transformations of very similar languages, the easiest way to go is usually declare one tree type that’s the union of all of them, then ignore the impossible cases at each stage. This takes the problem entirely out of the type system, true, but an ergonomic alternative for that hasn’t been invented, as far as I know. Well, aside from the micropass framework in Scheme, I guess, but that requires exactly the kind of rich macros that Rust goes out of its way to make ugly. (There have been other attempts in the Haskell world, like SYB, but I haven’t seen one that wouldn’t be awkward.)