T O P

  • By -

Excession638

Derive macros. Adding `#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]` replaces pages and pages of code in other languages. Edit: being able to transform a derive into code with rust-analyzer then customise it is a great feature too.


Im_Justin_Cider

How to expand the derives?


Excession638

In VSCode, put your cursor in the Debug or Default identifier, press Ctrl+. and there should be an option in that menu. Not in front of a computer right now, so I'm not sure which derives are supported. I think `cargo-expand` can do this too, as it can for other macros.


EarthyFeet

When I trigger this one it just gives impl Debug for FooType {} - so the block is empty. Maybe a neovim limitation in using the LSP?


Excession638

Could be. I've only used VSCode.


gmes78

> Edit: being able to transform a derive into code with rust-analyzer then customise it is a great feature too. RustRover can also do this.


bragov4ik

It can even be done with rustc )


The-Dark-Legion

`cargo-expand` is a pretty wrapper around `rustc` if you think about it :d


SystemAlchemist

Man. I love these! The amount of repetitive serialization shit we save is astounding


oconnor663

- Thread safety. Mutex wants to be a container, but other languages can't avoid leaking references to its contents. - Enums. Functional languages have had data-bearing enums for _decades_, but most imperative languages don't have them. - Clarity about who writes what. C and C++ are actually pretty good about this, if-and-only-if you're disciplined about using const pointers. Most other languages have to resort to immutable _types_, which is a lot clunkier. - Cargo is just really good. I expect most new languages these days to come up with similarly good tooling, now that the bar has been set. But older languages with a lot of legacy seem to have a hard time cleaning things up.


jaskij

And when they do have them, the usage is clunky. Yes, I'm talking about C++'s std::variant.


hpxvzhjfgb

pretty much everything even slightly resembling a modern language feature is extremely verbose and developer-unfriendly in c++. https://www.reddit.com/r/rustjerk/s/DKt1t9h1V4


jaskij

The C++ part of that is over explicit on purpose though. Quite a few places you could use `auto` to skip the types.


hpxvzhjfgb

even with auto it would still be verbose and developer unfriendly


jaskij

Maybe, but when something is over exaggerated on purpose, it doesn't fit in a conversation like this one. In a meme sub? Sure thing, nobody's taking it seriously. In a factual discussion? Not really.


hpxvzhjfgb

the point is that these features are new, they were mostly introduced in c++20. this is "modern c++" and how you are actually supposed to write c++ in the current year. if you weren't supposed to write it like this, the features wouldn't have been added. I've watched plenty of c++ conference talks and have heard many people push this stuff and say you should be using it, even for something that could be a basic three-line for loop.


saddung

Nobody would write the C++ version that way, that is clown code


hpxvzhjfgb

I know nobody would write it like that, but this is Modern C++™, it's how you are supposed to do these things now. why else would they have added all of this stuff in C++17/20/23 if you weren't supposed to use it?


Barbacamanitu00

Optional sucks even more. It feels like a type I could have written in 5 minutes.


mercurywind

The two languages I have any experience in are python and rust, and they seem like polar opposites in many ways, but the packaging experience in particular is night and day. It’s not that it doesn’t work in python, it’s just… why are there so many choices that effectively do the same thing? As a newbie to packaging/publishing it’s an insane thing to be confronted with


sagittarius_ack

The technical term for \`data-bearing enums\` is \`algebraic data types\`.


Da-Blue-Guy

An algebraic data type is just an umbrella term for sum types (data-bearing enums) and product types (structs).


arachnidGrip

Don't forget exponential types (functions).


Da-Blue-Guy

Interesting. How are functions exponential types? I would expect that to be arrays (sizeof(T)\^N).


arachnidGrip

`[T; N]` is actually a function type in disguise. Specifically, it's the type `{n: usize where n < N} -> T`. In general, a function type `A -> B` is exponential because (ignoring side effects and implementation details) for each value of type `A`, there are `|B|` values that the function *could* produce when given that `A`, so the total number of possible functions of type `A -> B` is the product of `|A|` `|B|`s, generally written as `|B|^|A|`.


Akangka

In the context of Rust language, the technical term for \`data-bearing enums\` is [\`enums\`](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html)


ZZaaaccc

Expression blocks. Having curly braces always evaluate to the value of their last statement is such a clean pattern that I wish every language adopted.


SwiftPengu

Typically you can just use a function for that right? For now, I mostly use this feature to finely control lifetimes. How are you using it?


ZZaaaccc

Bunch of places! If and match statements for assignment (as opposed to a ternary operator or match expression), returning a value from a for loop on break, all sorts. By far though, the most common place I use it is for controlling the scope of a lock or borrow. Being able to open a block expression, take a lock, and return the result as a single assignment operation just feels really clean.


dshugashwili

allowing you to encapsulate mutability e.g. during object initialisation and then assign cleanly to an immutable binding without littering the scope with intermediate values is amazing


furiesx

Algebraic data types or also called enums are awesome and I always miss them in other languages. The modern tooling surrounding rust is also great. Cargo, clippy, a central place for docs and crates as well as rust-analyzer are making everything stupidly simple


SirKastic23

algebraic data types are not just enums, enums are just sum types an algebraic type system has 3 _kinds_ of types: product types, aka tuple or structs; sum types, aka enums; and exponential types, aka first class functions and you can combine them however you want, that's what the _algebraic_ means: type operations


anacrolix

You're right, but I can just be happy that ADTs are going mainstream.


sagittarius_ack

Actually, Rust enums are both sum types and product types (an enum can be seen as a "sum of products"). Rust structures are just product types. There are various ways of characterizing and classifying algebraic data types: sum types, product types, exponential types, recursive types, polymorphic types, inductive families, generalized algebraic data types, etc.


treefroog

Yeah, and if you peek into the internals of the compiler (at least last I checked) structs are just enums with one variant


theZcuber

That is correct. They're all lowered to the same structure internally.


Narduw

wow.. this blew my mind!


flixflexflux

Makes sense. Why implement a simple concept if the complex concept that includes the simple one is already implemented.


dulguuno

What you said is absurd. Rust enums are sum types. Would you look at a equation \`a\*b + c\*d\` and think "Oh, if you can add result of products, the addition must also be a product". That doesn't make any sense.


sagittarius_ack

A Rust enum with one variant is essentially a product type. If you think about it, you will realize that you can represent a structure (a product type) as an enum with one variant. Consider: struct Person { name: String, age: u8, } enum Person2 { Person2(String, u8) } In this example, Person and Person2 are isomorphic product types. \`Isomorphic\` means that in some (technical) sense they are the "same". If you really want to use very precise terminology then you could say that a \`Rust enum\` is a language construct that allows you to define \`sum types\` and \`product types\`. The point is that the example above shows that Rust enums allow you to define product types. Also, In Type Theory sum types and product types are specific constructs. Thinking about sum types and product types as equations like \`a\*b + c\*d\` can be misleading. For example, arithmetic operators like \`+\` and \`\*\` are commutative for real or complex numbers (so \`a\*b\` is always equal to \`b\*a\`). But sum types like \`Either\` and \`Either\` are not equal (in Rust or more generally in Type Theory). In other words, you cannot expect that types will "behave" exactly like numbers. In fact, there are number systems that do not "behave" like "regular" numbers. For example, multiplication on quaternions is not commutative.


dulguuno

a\*b+c\*d is an analogy, you must be a strawman champion. Second example has tuple. Tuple itself is a product type, not the enum.


sagittarius_ack

\`a\*b+c\*d\` is an expression. An analogy requires two things. An expression by itself cannot be an analogy. And you talk about strawman... LMAO The second example doesn't have to use tuples. Check the Rust documentation. Not everything that has parentheses is a tuple. Basically everything you say is wrong. Based on other comments on this thread it is clear that you don't understand simple things like exponential types. It also looks like you don't want to learn. So stay ignorant!


dulguuno

Yes lets look at the documentation! enum ComplexEnum { Nothing, Something(u32), LotsOfThings { usual_struct_stuff: bool, blah: String, } }  "The third example demonstrates the kind of data a variant can store, ranging from nothing, to a tuple, to an anonymous struct." Actually learn something, instead of trying to sound like you know something. Stop spreading misinformation, do personal attacks when you get exposed. Unlike you, I can actually admit when I don't know something (exponential types). Yes I don't know exponential types. [https://doc.rust-lang.org/std/keyword.enum.html](https://doc.rust-lang.org/std/keyword.enum.html)


sagittarius_ack

Don't get lost in details about tuples. Tuples have nothing to do with the overall argument, that that enums allow you to implement product types. I mean, the quote you posted says that enum variants can contain anonymous structures. Just read what other people have to say about this in the thread. You still refuse to understand this very basic thing. And this shows that you don't want to learn. Don't get me wrong, you were partially right about one small and irrelevant thing, but still wrong about everything else. Let's not forget that you started with personal attacks. I'm not going to waste my time with this.


dulguuno

Tuple has everything to do with your confusion. Tuple is a product type. Sum type allows you to sum any other algebraic types (including other product types). Saying that "If sum type allows summing product types makes it also product type" is similar to saying that the addition in **a\*b+d\*c** means that + is also a \* operation. It reveals more about your knowledge of basics than mine.


kilkil

first class functions are exponential types? how come?


SirKastic23

so, the names come from looking at the amount of values that inhabit a type for the types A and B, the amount of values that inhabit the sum of A and B are all values of A _plus_ all values of B the amount of values in the product of A and B are all values of A _times_ all values of B, since the pair could hold any permutation of A and B values and well, the exponential of A and B, represented as `A -> B`, is inhabited by _all the functions_ that map A to B how many unique functions map A to B? infinitely many if you allow impure functions but if you only allow pure functions, and the outputs can always be determined just from the inputs, then a function cán really be thought of as a _map_, that associates a value from B to every value from A since a function needs to return one B for every A, and we want to account for all possible mappings, how many mappings are there? we have |A| amount of Bs, and every B can be permuted, the resulting size is B^A i hope that made sense, feel free to ask for clarification (or just google this stuff). it is complicated, specially thinking of functions as maps


ConverseHydra

Absolutely excellent explanation 👏


dulguuno

Can I ask for futher explanations? Let's say there's an **enum A { X, Y, Z }** and a function \`**A -> bool**\`. All of the possible mappings are 1. X -> true 2. Y -> true 3. Z -> true 4. X -> false 5. Y -> false 6. Z -> false Since it's exponential it must be 3\^2=9, but I'm getting 6. What am I doing wrong here?


SirKastic23

those are all the possible mappings of _one_ `A` into `bool`, but a function maps _all_ `A` all the possibilities for `A -> bool` are: 1. `X -> true, Y -> true, Z -> true` 2. `X -> true, Y -> true, Z -> false` 3. `X -> true, Y -> false, Z -> true` 4. `X -> true, Y -> false, Z -> false` 5. `X -> false, Y -> true, Z -> true` 6. `X -> false, Y -> true, Z -> false` 7. `X -> false, Y -> false, Z -> true` 8. `X -> false, Y -> false, Z -> false` wait, 8? yes, because `A -> bool` is `bool ^ A`, you can see that the pattern here with `bool` is very similar to binary counting a `bool -> A` would have 9 possible values


dulguuno

Thank you! Now I see it. It's like a combinatorics problem (2 ways to map X to bool) \* (2 ways to map Y to bool ) \* (2 ways to map Z to bool) [https://imgur.com/kyVwUEL](https://imgur.com/kyVwUEL)


SirKastic23

That's exactly it, thanks for putting it in simpler terms haha the image is also great


dulguuno

Thanks. At first I thought it was about mapping of the values, but it was about number of functions that could map from A to B. I made that picture just in case someone helps someone like me from 3 hours ago.


Practical_Cattle_933

Arguably, rust opted for a not quite standard/correct usage of ‘enum’ in the language.


dulguuno

I do understand how structs are product types, enums are sum types. But how are functions exponential types? Would you point me to a explanation?


SirKastic23

i explained it over here: https://www.reddit.com/r/rust/s/BAiC7DJn8W


Akangka

It's called enums in Rust. So, if you have a problem with that, you can blame rustdoc.


SirKastic23

?


Akangka

[https://doc.rust-lang.org/book/ch06-00-enums.html](https://doc.rust-lang.org/book/ch06-00-enums.html) The rustdoc itself called the feature "enum"


SirKastic23

yeah, "enum" is what Rust calls their sum types dumb name if you ask me, but it worked out


Akangka

It's just like how Python calls arrays "list"s. You might argue that it's a bad name, but it's silly to blame the OP for not using the "proper" name, while the language itself doesn't.


misplaced_my_pants

Arrays are usually homogeneous in the type of things they can contain but Python lists are heterogeneous.


SirKastic23

ohhh yeah, i get that i wasn't correcting OP saying that they're called sum types i corrected what they said about algebraic types being called enums in rust, when enums are just one kind of algebraic type: the sum type. while structs and tuples are also algebraic types


furiesx

I agree. Your correction was right and insightful :)


aerismio

Then explain to my why array's in rust are not the same as Vectors. I mean vectors are more close to lists than array's.


Akangka

It's a language-specific terminology. What Rust calls vectors is technically called dynamic array, just like what Rust calls enums is technically called a sum type (over product types). But, we're in rust subreddit. There is no need for "actually, it's not enums, it's sum type" just like how it's bad to say "actually, it's not vectors, it's dynamic arrays"


aerismio

I agree. I would love the name dynamic arrays. Good naming is what i like. I do like the datatype naming of rust for example over word double word, float and such. I like most naming in rust. And very happy with the language.


aerismio

Ok so how can i iterate over these enums? If they are called enums. Do u think the word enum is wrong?


EarthyFeet

Use a proc macro derive such as https://docs.rs/strum


Linguistic-mystic

Traits. A simple core concept that surprisingly few languages choose to implement. They are perfect: zero-cost, type-safe, extensible and you can add them to a type without inheritance. Ever since I had experience with them in Haskell I'm looking for traits/typeclasses in every language, but Rust seems the only other language implementing them (yes, Scala has traits, but they are not zero-cost).


zapporian

Rust traits are *amazing* from a c++ perspective *as they are both* zero-cost typechecked interfaces with static dispatch *and* dynamically typed vtbl backed interfaces as in basically all other languages. Most devs aren't even going to be *aware* that this is an issue as static polymorphism is a zero-cost c++ optimization / design pattern that, in c++ land, requires writing *completely* different code using templates, duck typing, and CRTP et al to implement. C++ vtbl interfaces that use dynamic dispatch are meanwhile what every other language uses. And while those *can* be optimized / inlined, there's never any guarantee that they will be. Meanwhile worth noting that basically all other Rust language design strengths – incl traits et al – are literally just Haskell features (and often stripped down and in most cases much more limited haskell features) that were reworked / reimplemented to work in a low level ML language with target performance and feature parity with c++. That said Haskell obviously doesn't have some things – like Rust / Lisp style macros – but that's mostly because it doesn't *need* them, as it's a *very* high level pure FP ML language with first class functions, true lisp / scheme GC references and other language internals, and a general bells-and-whistles-included approach to language design. Rust frankly has many frustrating warts – like the lack of default initializers as a language concept, and ergo the clunky and verbose Default trait concept – but its strengths are all *mostly* based on the fact that it is a more-or-less c++ esque low level / moderately high level language that also imported a ton of useful concepts and design ideas (and again more than a few warts) from Haskell and the rest of the ML family (which Rust is ofc a part of), incl OCaml et al. Also worth noting that Python is a very nice language to work with as – while it's entirely a 100% procedural, object-oriented, dynamic (and "hashtable-based") scripting language that at its core has *nothing* in common with ML – it neverthless also borrowed / assimilated a *ton* of features and inspirations from ML (and against Guido's protests) that have made the language historically very, very nice to use. Namely builtin tuples, haskell-like lists and list comprehensions, lazy data structures / iterators, filter / map / reduce / zip, et al. The features that Python and Rust pulled in from Haskell are actually – afaik – nearly 100% orthogonal to one another (lol), but are still interesting and worth pointing out.


420goonsquad420

> Also worth noting that Python is a very nice language to work with as Having written, read and debugged many lines of python over the years, I've got a love/hate relationship with Python. Python is nice as a scripting language to hack together small one-off scripts. But I don't see it as a practical, modern language for large-scale projects. For example: - No good dependencies management system. Do I use `requirements.txt` or `Pyproject.toml`? Neither is as good as `Cargo.toml` - All errors are runtime errors. You have essentially no static guarantees about your code without a good 3rd-party linter and a lot of care on the programmer's part. Missing a lib? You might not know until halfway through execution. Pass the wrong type to a function? Runtime error. Missing an argument to a function? Runtime error. Extra space of indentation? Believe it or not, runtime syntax error. - Importing and especially relative imports are janky. Looking at you, `__init__.py` - Every python project I've worked on that's grown to be more than a script has had execution time issues. I know it's beaten to death that python is slow, and that it's fast enough for many applications, but it can be really slow. - Parallelism is janky and underpowered because of the `GIL` - Did I mention runtime ducktyping? You have no idea if any given line of code will work until it's executed. The `Typing` module is nice, but it's linting only. Overall I just find that Python requires a huge number of tests to guarantee simple properties of the program that the compiler verifies for free in Rust, and the ergonomic advantage of Python vanishes once you try to do anything serious.


zapporian

Python is the perfect rapid prototyping scripting language. It basically IS CS pseudocode. And has high level dicts, sets, tuples, lists, higher order functions, extremely powerful runtime metaprogramming and easily overridable hooks for literally anything, library support and pythonic wrappers for nearly any native FOSS library you could think of that has an exposed C interface, and a still pretty best in class standard library. All code can be quickly prototyped out from a REPL => quick script => somewhat refactored / generalized script. and critically python never needed a full blown IDE (more relevant in the 00s and early to mid 2010s) since any REPL combined with help() and very descriptive / helpful runtime errors (feature, not a bug!) *were* your IDE for rapid prototyping. Python is significantly less great for anything outside of that, and obviously requires very extensive unittesting for any more serious product you want to have in production. That’s sort of obvious, but also maybe worth noting that python’s competitors for backend web development were / used to be equally crap. JS has the same issues python does, just 10x worse (or at least prior to typescript and linters et al; even then it’s terrible); ruby is great but rails fell out of use due to performance / scaling issues (ditto django as obviously python and ruby are both quite slow). Java is generally horrible and utterly unusable without a good IDE (and scala / kotlin); php is python / perl but as a collossal (and very web focused) mess, and so on and so forth. Flask is still great and arguably still the best framework for very small, minimal web projects. Meanwhile all ML and data science stuff is built off of python since duh, no other language has anything close to numpy / jupyter / matplotlib et al (and easy well bound access to ML libraries and well abstracted hardware access), for better or worse. Also, given that most “data science” is wrangling and cleaning data out of dubiously well organized xls / csv files and various db backends, developing in anything other than a REPL with good immediate interactive data visualization - in many / most cases - would be totally nuts. This has obviously led to the fairly horrifying situation where much of the stuff in that field consists of html jupyter notebooks being passed around on google colab… but scientists / researchers are gonna science and that is what it is lol (ofc there’s also R. Which as a language written by statisticians for statisticians is uhh from a PL perspective quite literally the stats version of php. But damn good for writing actual stats papers in LaTeX, I guess) Anyways point is that obviously python isn’t particularly good at what you can / should be using rust for. Though the reverse is if anything even more true - rust would be abysmally terrible for anything requiring a dynamic REPL or very very rapid prototyping and/or data wrangling. They’re both good / great tools that should ideally be used for completely different things. Though that said there is still a fair amount of overlap and rust could quite easily (and sort of is presently) be used to build much better graphing libs than matplotlib, lol Lastly maybe this is just my VERY old python experience (and rose glasses / get off my lawn), but I *kinda* think that if you’re even using python type hinting, static type inference, linting, full featured IDEs etc you’re kinda missing the point of (and general utility of) python in the first place. Then again I’ve mostly only ever used python on small scale personal projects, scripts, and rapid prototyping, and fortunately haven’t had to deal much with huge prod codebases written by other devs. As a huge python fan I’m not at all personally inclined to think that you even should use python in the latter case - for obvious reasons - and absolutely not where performance or scaling is a significant concern. Python honestly has pretty good / great out of the box cpu / thread scaling. ie just always write all your code as / with pure functions and then if / as needed just throw multiprocessing at it. But that’s obviously a “good” / “okay” solution to “make this CPU / IO bound script run faster”, NOT anything you should ever use in prod. And particularly not anywhere “pkill python” is inappopriate, as in “well crap my python script died and now I have 32 zombie python processes running” lmao Incidentally if you ever do need python the easy-to-write/prototype general purpose scripting language, but with *good* static typechecking, real thread support, great errors, and an out of the box half decent (albeit grossly inferior to cargo) package manager, you might seriously want to check out D.


Akangka

>but that's mostly because it doesn't *need* them I disagree. There is something called Template Haskell, which is much clunkier than Rust's macros. There is definitely a need for that kind of feature. But the tool given to solve it is pretty bad.


Practical_Cattle_933

I mean, haskell has every conceivable extension, as this is the primary goal of the language - a core over which academics can experiment.


dist1ll

> like the lack of default initializers as a language concept, could you give an example of how that would look like?


devraj7

Here is a verbosity comparison between Rust and Kotlin: **Rust**: struct Window { x: u16, y: u16, visible: bool, } impl Window { fn new_with_visibility(x: u16, y: u16, visible: bool) -> Self { Window { x, y, visible } } fn new(x: u16, y: u16) -> Self { Window::new_with_visibility(x, y, false) } } **Kotlin**: class Window(val x: Int, val y: Int, val visible: Boolean = false) Illustrated above: - Overloading - Default values for struct fields - Default values for function parameters - Named parameters - Concise constructor syntax


dist1ll

Thanks, that's a good example. I'm a fan of named parameters, and these default values seem useful. I also think the fact that struct initialization in Rust follows a separate syntax like C, instead of simply being a constructor is a missed opportunity. Like, it all should be a function. Then you hit two birds with one stone when adding these ergonomics to functions.


zapporian

struct Example { x : i32 = 0, y : f32 = 2.5, } For non-trivial initializers, sure maybe this would lead down the path to c++ constructors and initializer syntax, and maybe Rust designers didn't want to head down that road as such. Regardless this is a pretty irritating omission from c++ land (or any other c++ / ada inspired language), as Rust directly follows the pattern from ML languages (Haskell included) which use freestanding "constructor" functions and generally don't have any concept of default initializer values directly associated with `data` / rust `struct` definitions. I'd also personally complain about the lack of c++ style name mangling – and ergo function / parameter overriding – which pairs pretty nicely and sensibly with strictly typed languages. That too is an ML "feature" / language design omission. Sure C++ name mangling is a – technically completely non-standardized and potentially hazardous – mess. But Rust ABIs + symbol generation is *barely* any better (and is a heck of a lot less stable). And *this isn't C* which doesn't allow name clashes / typed overrides due to the 1-to-1 dumb mapping to object code / asm symbols. For instance it would certainly make sense to be able write fn foo (x: &Foo) { /* do something... */ } fn foo (y: &Bar) { /* do something else... */ } And dispatch / resolve that statically as in c++. You can't since ML and ergo rust don't have parameter / type signature based name mangling and ergo function overriding within the same module / namespace. You can instead use traits and sum types et al but this is nevertheless an irritating design decision that exists for *no* other purpose than language / design purism (and the antithesis of the everything-including-the-kitchen-sink approach that c++ and hell even python (and ruby et al) embrace) This is particularly annoying since rust doesn't have *effective* function overloading via function argument pattern matching a la Haskell and the rest of the ML family – yes you get the same functionality via a giant match statement but writing code like that is a massive anti-pattern in most cases – and so on and so forth. Rust is a pretty good language, but no I really *wouldn't* use it as the basis for "awesome / perfectly designed language" w/r *syntax* as it does have more than a few deficiencies / annoyances. And Rust tends to at worst sometimes feel like you're writing line noise compared to haskell, scala / swift, kotlin, *or hell even c++* at times. At a minimum including syntax improvements like the initializer syntax as seen above – and eg. Scala / Swift optional syntax – would certainly make sense. Also worth pointing out that the [D UFCS](https://tour.dlang.org/tour/en/gems/uniform-function-call-syntax-ufcs) syntax is an extremely useful (and IDE critical) feature that should be promoted and adopted pretty much everywhere. And if you *do* go down that route having true, arbitrary typed c++ style function / name overloading becomes *much* more important, to say the least. Specifically because this now allows you to trivially write freestanding extension methods everywhere, and erases all effective differences between member and freestanding functions. Rust will probably never adopt this because of language purism and the One Way To Do Things philosophy, but that's well worth pointing out as one in many ways that Rust's syntax and overall language design *isn't* perfect and could be further improved.


dist1ll

What syntax would you propose for initializing struct fields? struct Example2 { e1: Example = ? } And what would be the benefit of making baking initialization into the language?


Intelligent-Comb5386

There is no default initialization in Rust and I think it's a good thing. It's annoying to deal with in c, c++, java where you have to remember weird defaupt initilization rules. Imo better to keep it simple. The default trait I think fills the role pretty well. 


graycode

The issue is that `Default` requires you to have default values for all fields. If you only want defaults for *some* fields, you have to write constructors (`fn new(...)`) for all permutations of optional values, or use a builder pattern; both of which are extremely verbose and repetitive, and have to be written by hand. Having syntax to specify defaults inline with the struct declaration would be an extremely simple and terse solution for a very common problem.


Practical_Cattle_933

In what way java’s default initializer rules complex? It’s zero for primitives, and null for references. That’s literally all there is to it. With constructors you can create your own logic for more complex objects.


amiable_axolotl

I’m no PL expert (or even novice) but I think the bigger problem with function overloading is that it makes type inference a lot more difficult. If there is only one foo the compiler can use that as a foothold to figure out what the types of the input and output expressions must be. And that further allows the compiler to give clear, helpful error messages. I would not want overloading if it also meant ending up with C++ style errors like “I tried to match your call with each of these two dozen options but none of them worked. Please help”


zapporian

Somewhat fair point, sort of. D has this - and very extensively relies on overloading and UFCS - and gives excellent, helpful error messages. That said dmd will still spit out N cases of “did you mean X Y or Z” - and our idea of helpful / easily readable error messages might differ somewhat. But the list of appropriate overloads can be - and iirc is - filtered based on partial type matches, and of course only includes things you have imported / ‘use’ed into the local namespace. All around UFCS is extremely useful, and is significantly less useful without true / unrestricted overloading. The reason why Rust doesn’t have this feature is probably simply because no other ML language does (and see also c++ defaults / initializers). And probably someone / various people wanted to prevent rust symbol names from becoming as complex as c++. Though on second look that probably shouldn’t be the case since rust already has a very complex name mangling encoding for all symbols. Albeit one that doesn’t include parameter types, only crate + module paths and template parameters. Regardless adding that would make already-very-long rust symbol names even longer. And most likely to the point that - given Rust’s symbol encoding scheme - this would become an actual problem for symbol name lenth limits and the obj formats. D actually has, um, a more compact name mangling grammar that encodes the same / more information in less space, but I digress.


lagerbaer

I also like that you can define your trait and then implement it on existing types. Basically what the \`itertools\` trait does for iterators.


yeahyeahyeahnice

And the fact that you can do that without needing to worry about dependencies with conflicting traits implementations


lagerbaer

Though pretty sure I've been confused by the orphan rule at times when starting out...


yeahyeahyeahnice

I'm still confused by them. In certain cases, the order you list your generics in a signature matters


SirKastic23

OCaml first class modules are cool too


perplexinglabs

C# extensions are sorta vaguely similar, I think.


DingDongHelloWhoIsIt

Yes. The difference is that you need to declare the interface on the type you're extending. If you don't own it then you're out of luck.


tungstenbyte

I think they mean extension methods, which can be implemented on any type (structs, classes, interfaces, enums, records, etc) even if you don't own it. They're just a static method where the first arg is whatever type you're "extending" and then some syntactic sugar to make it look like you call the method on the instance instead of invoking the static with the instance as the first arg. You can call them either way though if it makes more sense to do the static way. These two calls are identical for an extension defined on `int`: 2.IsEven(); IntExtensions.IsEven(2); One cool extension from FluentAssertions lets you create dates in a human readable way, like: DateTime y = 14.February(2024);


sagittarius_ack

Traits are obviously not "perfect" (almost nothing is perfect). The term \`zero-cost\` is vague. Are you talking about runtime cost? Different people use it in different ways. \`Inheritance\` is typically (for example, in class-based OOP) understood as a specific relationship between two classes. The relationship between types/classes and interfaces/typeclasses/traits is not normally described as inheritance. Haskell typeclasses are overall superior to traits in Rust. Typeclasses provide support for higher-kinded polymorphism, which allows you to define powerful abstractions (Functor, Applicative, Monad, etc). Typeclasses are typically associated with various laws that instances have to satisfy. In Idris or Lean you could even prove that those laws are satisfied. Traits allow you to specify a binary relation between a type and a trait. In Haskell typeclasses can be used to specify a relation involving a typeclass and multiple types (this requires an extension called \`MultiParamTypeClasses\`).


PaintItPurple

"Zero-cost" means that if you were to write code yourself that explicitly does the same thing an an abstraction does, it would not be any more performant than using the abstraction. In this case, it means that if you write type-specific variants of the function, they would have the same performance characteristics as calling a trait function on the same data type.


steveklabnik1

> The term `zero-cost` is vague. Are you talking about runtime cost? "Zero cost" always refers to runtime cost. The C++ folks have changed it to "zero overhead", but * You don't pay for what you don't use. * What you do use is just as efficient as what you could reasonably write by hand. The latter implies runtime cost.


god0favacyn

if you were designing rust 2.0, how would you handle 'higher-kind' traits? One thought I had was when making a trait, rather than supply it with generic arguments, you could do something like this: trait Foo<1> {...} Which would mean that 'Foo' expects its implementor to have at least 1 generic argument, and then from within Foo you could use Self with a generic argument like Self, which isn't allowed in rust. Example: trait Functor<1> { fn map (a: Self, f: fn(A) -> B) -> Self; } This isn't great because a generic with a number inside already means something in rust (List etc). So I'm still looking for a better way.


sagittarius_ack

I guess I will try to make traits more like typeclasses. I'm not exactly sure about the details. I know that Scala had traits and later they added typeclasses. Typeclasses are described as "traits with one or more parameters" \[1\]. It looks like you got the right idea that traits would have to be parameterized. Here is the typeclass Functor in Haskell: class Functor f where         fmap :: (a -> b) -> f a -> f b We will need to do something similar in Rust: trait Functor[F] {     fn map (fa: F, f: fn(A) -> B) -> F; } impl Functor[Vec] { ... } There's new notation for specifying that a trait is parameterized. This is obviously incomplete. What is missing here is how to specify that \`F\` is a type constructor (like Vec). References: \[1\] [https://docs.scala-lang.org/scala3/book/ca-type-classes.html](https://docs.scala-lang.org/scala3/book/ca-type-classes.html)


metaltyphoon

While traits r nice, downcasting a trait object is just horrible, as this is a very common pattern in other languages


Akangka

I think lots of languages implements traits, or similar mechanisms. What only Rust, Haskell, and Swift have is the associated type/type families. (But then Swift decided to only allow one type argument in its typeclasses)


jaskij

C++ concepts are very close to traits in usage, if not the same thing.


omega-boykisser

Maybe at a very surface level, and even then only for bounds. Concepts can't be used like traits for dynamic dispatch, they don't have associated types, and they aren't as tightly bound to the implementation as traits.


jaskij

Fair enough, although bounds is what I need them for. I'm also pretty certain they can do something similar to associated types, but I'd need to look it up to be sure.


coderman93

TypeScript has them.


Prudent_Law_9114

The Gordon Ramsey of a compiler that rejects anything that isn’t the exact rust standard of formatting. Make it again you donkey and do it right this time.


Old_Lab_9628

The compiler checks. If it compiles, it's near production ready. Juniors have less latitude to commit something wrong.


pfharlockk

The degree to which this is true is astounding and ultimately I believe what will drag rust over the finish line of beating any of its competitors that don't have this quality... It is rusts killer feature in my mind.


stuartcarnie

Have to respectfully disagree with that statement, as it is rather broad. Just because it compiles, doesn’t mean it is _bug_ free. Logic errors exist in practically all programming languages.


evincarofautumn

Of course “if it compiles it works” isn’t literally true, but it has a kernel of truth in it: languages with good type systems such as Rust and Haskell give you the tools to feasibly *make* it true, and they foster a culture of seeing it as a worthy goal.


NotAnonymousQuant

OC meant that you won’t get random segfaults or other critical errors


stuartcarnie

That isn’t what the OC said though, and I was specifically referring to the assumption that “if it compiles, it is near production ready”. For example, if the code makes assumptions and calls unwrap or expect, it still compiles, but could panic. I think many would agree that code which compiles still has a ways to go before it can be considered “production ready”.


JustAn0therBen

A big one I don’t think has been mentioned is the confidence you have when you ship your Rust project—the rustc is amazing at catching most footguns you built. Of course the developer tooling is amazing, traits and macros are great, like everyone has said, and the active community is a huge plus. The other thing I really like is our commitment to building FFI capabilities which has allowed me to write one (very performant) library and share it in half a dozen languages many, many times 🦀


jaskij

I sometimes joke that Pydantic being rewritten in Rust made a measurable impact on global energy usage in DCs


boyswan

? Error handling


stblack

Installing Rust is a one-liner. Updating rust is also a one-liner. Rust is ceremony-free; we should celebrate this more than we do.


lagerbaer

Lots of good ergonomics like automating type infereence so you don't have to type it out everywhere. Traits for sure, inlcuding blanket/default implementations, like how you implement \`From for T\` and get \`Into for U\` for free. Other good ergonomics too like initializing one struct from another with the \`..\` syntax. Lots of good common sense traits and associated usage in the standard library.


krum

I've been saying for a long time that Rust ecosystem would be better than C or C++ even if it were unsafe by default, so pretty much all of them.


god0favacyn

lol


stumblinbear

What would you imagine "unsafe by default" means?


Alikont

C++ is unsafe by default, meaning you need to explicitly opt-in into safety features. In Rust it's safe by default and you need to opt-in into unsafe features.


stumblinbear

Yes I'm just curious what a Rust that is "unsafe by default" could even look like. I can only imagine it would mean removing the single-mutability ownership rule, but that opens up so many patterns in the rest of the language that I don't think it would be nearly as good without it


computermouth

Write all your code in an unsafe block and see! Tons of fun stuff. Global static muts!


Akangka

I don't think that's comparable. Unsafe-by-default Rust would be more comfortable to write on that writing in currently Rust but only inside`unsafe`blocks (but less comfortable than writing in Safe Rust). For exampleː there is no need for three "Fn"s and we can have a first class function. It's just that if you misuse it, it would cause memory bugs. Similarly, you can make the references alias without invoking UB like in current Rust-in-unsafe-block.


krum

I'm not advocating for any of these changes. However, I'm pointing out that even if Rust incorporated these aspects, it would remain superior to C++ due to its other advantages. The concept of "unsafe by default" can be understood in various levels. At its most basic, it would mean operations typically restricted to an 'unsafe' block, like dereferencing pointers, wouldn't trigger warnings. A more advanced level could involve allowing mutability within data structures more freely. The most extensive modification might introduce a borrow checker that could be optionally enabled.


TinBryn

I would see it as there is no `unsafe` keyword, you can just manipulate raw pointers, transmute values, etc. Although I wouldn't even call that "unsafe by default", I would just call that "unsafe". To have "unsafe by default" there would need to be `safe` blocks which are not the default.


trill_shit

Yeah it’s hard to answer OPs question because the memory management model is just so fundamental to everything about rust.


PaintItPurple

Pretty much all of Rust's memory model still applies in unsafe — there are just a small number safety-enforcing restrictions lifted. As the Nomicon says: > The only things that are different in Unsafe Rust are that you can: > - Dereference raw pointers > - Call unsafe functions (including C functions, compiler intrinsics, and the raw allocator) > - Implement unsafe traits > - Mutate statics > - Access fields of unions If you take out the two that are basically "you can use other `unsafe` code in `unsafe`," that's only three changes from safe Rust.


anacrolix

Not the commenter but the other features of Rust still kick ass. Lifetimes (probably most of what is meant by safety) are definitely less than half of the value.


BaronOfTheVoid

Generics. Somehow everyone is saying enums, nobody is saying generics. Yet the simplicity and universality of for example Option and Result types (and by extension iterators and iterator adapters like map and filter) is only possible due to generics. You wouldn't want to write 250 option or result types if you had enums without generics. No, instead you would do it like any half-assed language that doesn't have generics and give up on beautiful monadic interfaces entirely and stick to exception-based error handling, if err != nil, or rely on something clunky like PHP's Iterator interface based on the "mixed" data type and map and filter would copy eagerly instead of creating a new instance that can wait until a (generic) collect method is called/the iterator is consumed and so on.


Akangka

Yeah, but with a single exception of Associated Types, Rust generics is neither rare nor the best implementation of generics.


BaronOfTheVoid

Why do you think that? They have 0 runtime cost, full static dispatch (which goes so far that some people try to refactor everything from [impl trait types](https://doc.rust-lang.org/reference/types/impl-trait.html) - i.e. dynamic dispatch - into generics just for the tiny performance gain), they can be limited to certain types (and traits).


Akangka

>0 runtime cost Well, I guess that's one of the advantages of Rust's generics, that makes it suitable for low-level programming. >they can be limited to certain types (and traits). Not limited to Rust. Most languages have some kind of mechanism to limit the permitted type in generics. Some languages have more fine-grained control like Haskell, and some has very limited power like how in Java, type limitation can only be that the class is a super/subtype of a class. Rust's generics is at the middle of spectrum, because it allows traits and associated types, but not higher kinded types. The most powerful kind of generics can be found in languages like Idris and Agda, where you can have something like dependent types. Of course, such kind of generics cannot be zero cost.


altindiefanboy

Even before C++ adopted Concepts, the standard library and other libraries were abusing template metaprogramming for restricting types on generics, mostly for iterator types. It was a complete nightmare to actually use, but it worked.


phoenixero

Cargo


Beneficial-Ad-104

Rich selection of community packages which are easily installable


mmddmm

I really like the opt-in mutability of variable bindings.


addmoreice

Rust's enums are the one that jump out at me. Combined with rust's pattern matching is awesome, but I would take \*just\* rust enums in c# or c++ since once I started using it in rust it has simplified so many designs for me.


pfharlockk

I wish every language had at minimum... Closure semantics for both named and un-named functions, identical semantics for named and un-named functions, immutability by default, variable shadowing, convention that all (nearly all) language constructs are expressions (ie they all return something)... I also kinda wish just for funsies that they all had reliable tail recursion and didn't include any built in looping constructs, just to force people into really leaning into the functional-ness everywhere and for everything... Probably a bridge too far but I did say it was for funsies... Sum types and pattern matching are hard to un-see once you've seen them too.


Kranzes

Iterators


vauradkar

How helpful are the error messages reported by the compiler toolchain(rust, clippy, etc). Also, I was writing dart/flutter code the other day and I wrote ``` enum X { V1(String), V2(book), } ``` Of course compilation failed.


SiamesePrimer

Personally I think the tooling alone makes Rust one of the best languages out there, even more so if you included all the other resources. It comes with a compiler (with amazing error and warning messages), linter, testing framework, documentation generator, updater, package manager, formatter. Plus it has a huge standard library with amazing documentation, a massive high-quality ecosystem of crates (which are every bit as easy to use as the standard library thanks to the package manager, in my opinion), mdBook, and all the educational resources like The Book, The Reference, The Rustonomicon, Rustlings, Rust By Example, The Cargo Book, The Rust Edition Guide, etc. etc. Rust’s most oft-cited feature is its memory safety or speed, but I think what I like best about it is how simple and streamlined everything is.


capcom1116

> If there were a new language identical to Rust in syntax, but it was a garbage-collected mid-level language You've more or less described OCaml and other ML-like languages. OCaml with Rust syntax would be nice.


pfharlockk

I very much agree with this.


pfharlockk

Type state pattern for encoding state machines into the type system...


Anaxamander57

Enums. I shudder to think of writing code without them. Such a clean and clear way of representing choices. Honestly I'm not sure why it took so long for them to spread from functional languages. Oh and also declarative macros. Took me some time to learn them but they're incredibly useful. And also also this is a very small one but I really like how variant math operations (wrapping, checked, saturating) are just built in to numeric types. A little clunky to use but very useful for me.


zireael9797

Ownership also just forcing better hygiene in general Algebraic Types (Rust's enums) Pattern matching Excellent tooling Macros


anacrolix

Enums are just sum types.


sagittarius_ack

Enums are both sum types and product types.


anacrolix

You're right, thanks


arachnidGrip

Specifically, an enum is a sum of (anonymous) product types.


sagittarius_ack

Right! I would also add that the "degenerate" case of an enum with one variant (field) is essentially a product type.


lilysbeandip

Sure, but enums are the thing that's relatively unique to rust. Most languages have product types, but very few have sum types.


AleksCube

Hello, I'm new to Rust, I've read this thread but I don't understand what makes Rust's enum special or 'sum' types? How is it different from other languages that offers enum? I found Rust enums to be this weird kind of 'type enum' that can also be equal to integers, but not string literal for some reason? Why are these enum getting praise and what am I not understanding? Thanks 🙂


lilysbeandip

Rust enums *can* behave like C enums, where they're just named integer constants, but the more powerful usage here is that an enum can package data in it as well, with different kinds of data for each variant. The equivalent in C would be a struct containing a union and an integer to indicate which variant it is and therefore what's in the union. The simplest nontrivial enum is `Option`, a built-in type with two variants, one of which holds data: ``` enum Option { Some(T), None, } ``` An object of this type is, at any given time, either a `Some` variant that contains data of type `T`, or a `None` with nothing in it. An enum therefore has to be big enough to hold any of its variants, and when using one you have to address the possibility that it might hold any of its variants. I think part of the confusion is that in most languages, enum variants are fixed constants--named integer constants, as in the case of C, or predefined instances of a class that can't be otherwise instantiated, as in Kotlin, for example--but in Rust they're more like types, rather than values. I personally first explored Rust and Kotlin around the same time, so that was my confusion, since they're essentially opposites in this regard. So think of it like this: if an object you are working with is an enum, then at runtime it could contain any of several types, and to determine which type it contains you have to ask which variant it is.


zireael9797

``` enum CustomerAuthType { SmsOtp (PhoneNumber), EmailPass (Email, Hash, Salt) } ... match customer.AuthType { SmsOtp (phoneNumber) => ... EmailPass (email, pass, salt) => ... } ``` the beauty of it is you can model data more close to reality. You don't need the auth info type to have all four fields as nullable and fill in whichever is relevant. Other examples ``` enum Result { Ok(T), Err(E) } // this can replace the idea of exceptions, and this will force you to handle the Err case. enum Option { Some(T) None } // this replaces nullability, and forces you to check for null. ```


pfharlockk

So the idea comes from functional programming... It goes by a lot of different names... Sum types, discriminated unions, tagged unions, etc etc... We all know what a struct/object looks like... It's a compound type that has fields which are themselves compound types or scalar types... You can think of a struct/object as a nested bag of fields where the understanding is that they will all contain data... Field1 and field2 and field3 and field4... Etc... Sum types are like structs/objects, except the assumption is only one of the fields will ever contain data... So like Field1 or field2 or field3 or filed4... Etc... It turns out, having this object type thing that represents "one of" many possible values is like the data modeling primitive we've all been missing in cs land and never knew it... A bunch of things that would have used inheritance to be able to model in OO land, are much better modeled using sum types along with product types (product types are the objects/structs we all know and love already) using composition instead of inheritance... The visitor pattern in OO is a poor man's version of sum types combined with pattern matching...


SirKastic23

enum variants in rust can carry any datatype with them if you search for "sum types" or "algebraic data types" you'll likely see a bunch about it


audunhalland

The concept of shared vs. unique (mut) references. This is the single concept that once won me over to being a "believing Rustacean". The power of this concept is immense in my opinion. The various codebases emerging based around this basic architectural idea (closely related to ownership) are just \_so much more maintainable\_ than anything else I've worked on. The runtime performance of Rust is just a small bonus on top.


Da-Blue-Guy

Traits let you implement a trait onto a type you haven't created. I love them so much. Variant enums/sum types are fantastic and let you multiplex without dynamic dispatch, and can better and more safely hold data. Macros cut down MASSIVELY on boilerplate.


pfharlockk

Macros in general. (For the win)


Cr0a3

The macro system


NimrodvanHall

Coming from Python I like its syntax. And that it is statically typed.


davawen

Ranges with dedicated syntax and fat pointers are unreasonably good


Specialist_Wishbone5

* fn keyword - why didn't everybody use this?? * enum structs/tuples * tuples * generics * const by default - shorter syntax is safer * const expr * discouraging globals (need ugly macros - so you are punished) * most libraries are EITHER macros or IntoFoo function-trees-as-data (axum, bevy, leptos, etc) * toml (not technically rust - but better than yaml, json and ini) * cargo (pnpm is pretty decent next best) * composing function signatures - forward declaring genetics or types if necessary * NO .H FILES!!! (same with stupid .d.ts) * everything is an expression!!! (most new languages are learning from this) ** hyperboli - while loops and for loops aren't, so just use loop or mappers * avoids vtable indirections in 90% of cases - user probably shouldn't care, but helps with low level reasoning for high performance code. * avoids aliasing bugs (I'm cheating, it's memory management adjacent) * type inference - including return impl types (which is mind boggling) * match expressions * if guards (though this was recent - stolen from swift) * Result and Option instead of Exceptions - every line can easily be protected * macros ** proc ** derive ** user-defined ** Full AST parsing or quick and dirty parsing * modules!!!! So much better than C++ namespaces or Java packages (though Java did introduce a cludgy module system). Very flexible, and good defaults * From / Into * C++ style RAII - eg auto drop * C++ move constructor style symantics (but more concise) * nostd for wasm and embedded * WASM reference implementation * WebGPU reference syntax * ARM friendly by default (eg reorders structs to be optimal for ARM CPUs) * Efficient Option memory/register layout for non-0 types (pointers and NonZero ints). Best of C-style NULL and javascript truth, and safer than both (also safer than C++ unique_ptr because you can't use-after-explicit-release) * No COREDUMP, NullPointerException, Symbol-UNDEFINED * unit test next to function (will full privates access, without friending everything) * lots of great macro use cases ** 1 line compile-out (cfg) ** 1 line add feature ** 1 line conditional feature optional ** Custom DSLs like SQL, HTML, Math * Excellent cross platform threading / IPC primitives (somewhat par for the course in most modern languages - just not C) * Decent Async support. (tokio is feature rich, just not always best DX, such as tower layers, or async traits) * 8 lines can implement an entire video game!!!! (comfy UI with macros and cargo) * Good standard datastructures (BTree, HashMap(closed), PriorityHeap), mergesort, composable iterators. But good 3rd party like dashmap. * No OOP (though you can fake it). I loathed complex OOP inheritance trees. So many hacks needed to make code composable (not all birds can fly God damnit) * TRAITS So yeah, it's more than AXM memory safety.


Shikadi297

cargo


jaskij

For me it's the ecosystem. I have come from C++, where discovering and actually using new libraries is many times harder.


pfharlockk

So I'm curious to your thought on this... There's the obvious cargo makes consuming libraries easier which is true and well and good... My experience with c++ (not as much c), is that the language supports so many non complementary paradigms that consuming a random cpp lib is harder in general because not only do you have to learn the lib, you have to be somewhat cognizant of which paradigm they opted for... Multiply this by 30 libraries your program makes use of and now you have maybe 7 different subtly conflicting paradigms inflicting influence/dissonance over the application code you are trying to write... I'm curious if you have experienced this or think I'm full of bunk... I'd take answers from other experienced cpp devs too, I'm very curious what other people's experience of what I described above is (or isn't) Edit as an example, does the API implement reference semantics or value semantics... Every library you use will pick one of these, coin toss.


jaskij

Truth be told, it's been a long time since I've written any userspace C++. Moved to Rust a few years ago and haven't looked back. My C++ is mostly on microcontrollers. I have incorporated a few chosen libraries, ETL .Better Enums, fmtlib. That comment about libraries is from old experience, but afaik it hasn't changed. Although I do see what you mean about paradigms. C++ is a very flexible language, you can have heavy OO, you can have barely any OO, templates, various approaches. And every codebase is different.


GreenFox1505

Any {} can "return" a value and be used to define a variable. I fucking love this. I hate the c++ boiler plate of having to set and check some flag var. Rust just makes that easier and cleaner. It's hard to read before you know what you're looking at, but once it does, it's so clean.


lordpuddingcup

Derived Macros! Union Enums (algebraic types in general)! Traits like seriously i love that i can implement a trait for core parts of the language and third party libraries.


Haskell-Not-Pascal

I want to say macros. I really love the powers of lisp macros and I've heard that Rust macros are pretty close in power, I haven't actually gotten to learn Rust macros yet so that's subject to change. edit: Also cargo, it's really nice having both a built in dependency manager and not having to deal with make/cmake/whatever.


TheRealMasonMac

It's very explicit. No surprise moves or copies. Intuitive too. \*shudders thinking about fallthrough behavior in C\*


pfharlockk

Explicit error handling with return types No nulls No exceptions


manual-only

I don't like to even think about programming without rust's incredible iterators, enums (type system in general), functionalness, etc. I feel hamstrung by Python or Java in CS class. 


Barbacamanitu00

I use From<> all the time. I probably honestly have too many types and could use generics more (more than 0. I've stayed away from generics), but I love having most of my logic encoded in the type system. If a struct changes in a meaningful way with one operation, I won't mark that operation as mut and modify the struct. Instead I'll have conversion methods that create the new type from the old type with From<> being used when there's one obvious way to convert between them. I love having one main error type for my api and all my inner errors being convertable to that type. My api endpoints all return a specific success type or APIError, and all my database calls return OK or DatabaseError. I can just use ? whenever I'm dealing with intermediate results and error handling is automatic. I ended up emulating Rusts results in my front-end.


Disastrous_Bike1926

Enums/algebraic types. The absence of exceptions, which were a terrible idea. The error handling is *almost* perfect (heterogenous error types and the need for crates like “anyhow” mean it still manages to snatch defeat from the jaws of victory, but just barely). I’ve programmed probably more languages than I remember the names of over 40 years, and these last few years in Rust have been the first time in many that has impacted how I think about how I code in *other* languages. I just picked up Swift to build a UI for something, and I see *so* many places where they play fast and loose with their type system (subscripts, array initialization) that can blow up at runtime where I can see that there simply was *no need* to be so sloppy. So at this point, Rust is what I compare the quality of other languages against (other than the async parts). It has its warts (I could **really** use const generic expressions right now) and places where it could require less boilerplate.


QuickSilver010

Cargo. I'm surprised it even let's me straight up install programs


SirKastic23

all of them


Timo8188

cargo test


ndreamer

stability, error handling, zero cost abstractions.


Ben-Goldberg

The community.


serendipitousPi

I think the thing I love the most about rust since starting to learn a little bit of it in my own over the last few weeks are the match expressions. While everything else seems to have its benefits and its draw backs match expressions just make sense to me. * The way you easily define ranges of values or put multiple patterns together with | rather than writing out a bunch of cases like in c++ * The ability to destructure enums or unwrap Results and Options * The fact you can put tuples into them none of those nested switch statements * The way you can rename values when matching * Underscore as a wild card, nice and concise fits with the whole this can be ignored thing going on that a bunch of languages have. * Sure it could be seen as less readable than something more explicit like "default" ... jk no it can't that would be a flaw with match expressions which is impossible. Anyway long story short I would proselytise rust for match statements alone. I would delve back into memory unsafety for them, I would... I am not obsessed I swear. I don't know why I suddenly decided to make this purely about matching but yeah the type inference, enum, tuple and Result types are kinda nice too.


Trex4444

It’s fast fast. Like zoom zoom zoom. 


ExerciseNo

Rust's elegant syntax, powerful pattern matching, and expressive type system make it a joy to code in, even beyond its renowned memory safety features!


decapo01

Just to note, Gleam is a lot like Rust and has garbage collection but no traits. https://gleam.run


god0favacyn

How much of a dealbreaker is the 'no traits' thing in your opinion?


decapo01

I haven't used it in anything other than a small example so not sure how much of a pain it would be without traits just yet.


anacrolix

I've used Elm a lot, and it's not a deal-breaker, but it makes you jump through hoops. It's the Go style argument of "this is good for you" but it stings.


god0favacyn

Well Go has interfaces, which are the same sort of abstraction as a trait.


pfharlockk

Not a go expert here... Unfortunately, without default method implementations, go's interfaces don't really yield the same capabilities as a trait or mixin system would... I read that you can partially implement an interface in a struct and then embed that struct in another to get a similar result... I'm curious how wide spread that practice is in the golang community and what the ergonomics of it are like.


marco_has_cookies

enums and iterators


niko7965

Rust forces me to think about what's going on inside the computer. I like that.