Hacker News new | past | comments | ask | show | jobs | submit login
How I went about learning Rust (thegreenplace.net)
347 points by zdw on July 15, 2022 | hide | past | favorite | 292 comments



I tried to pick up Rust a few years ago, but there were too many sharp edges. I thought it was a nice language, but was too early for actual use. I played with some libraries (Apache Arrow, etc) and it was nice.

About 2 months ago I wanted to use Arrow within Elixir, which required me to start using Rust again (Elixir uses Rustler to safely convert from Rust <> Elixir without theoretically crashing the beam). I am amazed how far it's come, and I've actually been using it a lot over the last few months and haven't ran into any major issues. There is a fair amount of information out there, and the documentation as the OP pointed out is really fantastic.

The libraries are really good now, and I've had no issues using it on Linux or on my Macbook M1. Incredibly impressive speed results. SIMD is a game changer.

Major shoutouts to arrow2 and Polars, I love using it for data analysis :).


I mean it depends. Many of aspects of Rust that are perceived as sharp edges are in fact the programmer bringing in their preferences and paradigms from other languages and trying to program that way in Rust.

I was one of those and tried to do OOP in Rust. It was a pain. At some point I gave up and was like: "Okay Rust, I do it your way, I just want this to work". And it worked flawlessly and easy. I literally had to overcome my ideas of beauty in code to realize how Rust is meant to be programmed in (more data oriented, less object oriented).

This was a really good lesson for me, as I had to question similar aesthetically motivated decisions I made in other languages in the past.


Rust actually supports most OOP features, with the main exception of implementation inheritance. And implementation inheritance is a nasty footgun in large-scale software systems (search around for: "fragile base class problem"), in a way that just doesn't apply to simple composition and pure interfaces (traits). So it's hard to fault Rust for including the latter and not the former.


For me it was the web-of-pointers strategy I had to unlearn. A child object keeping a pointer to its parent is misery in Rust. It forces you to either make the child completely independent or really prove the parent will be around until the child disappears.

90% of the time this is dumb overhead, but 10% of the time it found a bug in some edge case, so I learned to appreciate it as a tough teacher, and my designs got better for it.


We solved this with flat vectors and just sharing index values in cheap walker objects. It is much nicer to work with compared to arc/weak pointers.

Code here: https://github.com/prisma/prisma-engines/tree/main/libs%2Fda...


How is this fundamentally different from raw pointers?


Not OP

It serializes better, it's memory safe, it can be much faster in performance terms, you can get better memory usage if you're holding a lot of "pointers" because the indexes don't need to be 64 bits.


But you lose all ownership tracking / safety that Rust is famous for.

I just can't help but wonder what it means for that paradigm, when the most popular answer to "how do I model my entities in safe Rust" is basically "backdoor the safety".


Which way did you go, (1) not use pointers from child to parent (functional programming approach), or (2) kept using such pointers but made it work? Your first paragraph sounds like (1), but the second sounds like (2).

If it was (2), it sounds like you made it work without unsafe code? Can you share some code?


> designs got better for it.

What is better about them?


>"90% of the time this is dumb overhead, but 10% of the time it found a bug in some edge case, so I learned to appreciate it as a tough teacher, and my designs got better for it."

Sounds kind of like Stockholm syndrome


And you sound like you are part of the reason why we still have exploitable null-pointer bugs in 2022. Imagine a structural engineer that would say "all that static and dynamic analysis is dumb overhead 90% of the time, so I am going to skip it". Imagine an electrical engineer who would go like "all these wire gauge calculations are exhausting, 90% of the time my installations don't catch fire"

Seriously, I sometimes wonder what is wrong with the whole field of software development.


Nothing is wrong. Same shit as in any other area. It is a usual trade-off between what you pay and what you get in return, some of it highly politicized.


> Rust actually supports most OOP features, with the main exception of implementation inheritance.

I've tried some basic OOP.

Fields from the base class need to be redefined in each child, and making shared methods private require an ugly workaround. Both concepts are very basic OOP concepts, not advanced or inherently/tendentially dangerous, and the results is that even basic Rust OOP requires a lot of boilerplate. It's workaroundable with macros if one wants, although they have some limitations, and I guess this is the reason why the macro approach is not widespread.

Some devs use composition to emulate it, but the result is another form of boilerplate (very noisy builder patterns).

Regarding "non-basic" OOP features, I remember that Rust GUI framework programmers uniformly complain about Rust not being appropriate to translate the OOP hierarchies typically used in GUI frameworks, so the gap is significant (this is not inherently bad; not supporting OOP can be a respectable choice, but matter of factually, this does create a gap in some cases).


You can define a field with the type of the base class in the subclass, and impl Deref for SubClass, with Target = BaseClass.


You can, but the docs for Deref beg you not to use Deref for types that aren’t smart pointers because of how confusing and surprising the results can be to an unfamiliar reader of the code.


Rust itself does not have much support for delegation (the building block for the features you're referring to) since it's pretty much syntactic sugar anyway, but you can use crates like 'ambassador' to provide that.


The idea of object inheritance is silly. If you need a 'base' class A for data type B just do

    struct B {
      a: A
    }


Does OOP require inheritance?


Kind of, it needs the ability to express polymorphism and extend concepts.

Now how that is done is practice, can be done in various ways.

Class inheritance, which is what many think of.

Interface inheritance or conformance, which is available in many languages via traits, patterns, data classes, functors, or other names, and OS ABIs like COM.

Prototype inheritance, clone a instance and monkey patch in place (like SELF)

Via composition and delegation for not handled messages.

Then many languages offer a combination of those approaches.


For me, one of the main reasons to use OOP is dynamic dispatch / runtime polymorphism. I have not spent much time with Rust, but it seems like that is a bit cumbersome there, and doesn't exactly work like you would expect from Java, C++, Python, ....

I mostly use OOP when I have a bunch of Foos and Bars, and want to treat them differently in some places. Instead of having ifs in each function, I use inheritance, and override the methods that I want to treat differently. It's mostly about heterogenous containers and avoiding explicit ifs. When you use it that way, it's safe and convenient and I've never ran into the fragile base class problem or other typical issues.


It definitely works differently to C++/Java/Python, but IMO this is one place where Rust is a huge improvement. You are effectively always generic over an interface (trait in Rust) rather than a base class, and this is much more flexible as a given strict can implement as many interfaces as it likes rather than being stuck in one class hierarchy (or having to deal with the pain of multiple inheritance)


It is no different than using interfaces, protocols, pure virtual base classes....


It's similar, but there are some differences. Notably, my package can implement my trait for your type, while my package (usually? always?) cannot make your type inherit from my virtual base class.


Depends on the flexibitly of the language, there isn't a golden way to do that across all variations of OOP.


Wrapper classes should be equally good once Valhalla (jdk) lands, without introducing another pointer indirection.


Interesting but Java gets more and more complex in a way that does not make me want to write things in Java.


Not a Rust expert but: doesn't Rust support this use-case via Box<dyn SomeTrait> , and leveraging default Trait methods, only overriding the defaults you want to for each type implementing the Trait?


What you describe can be achieved in Java via composition (instead of inheritance) and using interfaces.


Does it?

I've seen it being repeated ad nauseam without any concrete backing.

I mean it has some OOP concepts. But it's mostly in traits. Saying Rust is OOP is a bit like saying Chimera is a Goat.

Anyone that tried to use OOP in Rust, knows it's next to impossible.


Yes it does, OOP is a spectrum not "like Java does it".


I'd consider something OOP if it has the same expressive power i.e. things expressible in OOP languages are easily expressible in another OOP language as well. And for Rust that's not the case.

Otherwise you end up with Haskell is an OOP language.


I will debate it is one, yes.

Just like being an FP language isn't "like Haskell does it", when I learned FP, Haskell didn't even exist.

Try to express Eiffel, CLOS, SELF or BETA in Java.

By the way, here is One Weekend Raytracing in Rust, perfectly using OOP with dynamic dispatch and interfaces (sorry traits).

https://github.com/pjmlp/RaytracingWeekend-Rust


Ok. But noticed what I said. *Things* that are easy in one OOP should be similarily easy to express in other OOP language.

Counter example: Html5 DOM.


Counter example of what?

A graph representation of nodes based on JavaScript object model?

Fine, https://rustwasm.github.io/docs/wasm-bindgen/examples/dom.ht...


Yeah but if you dig deeper you'll notice it is riddled with unsafe, casts and Deref misuse.

It's a brittle simulation of Inheritance. To transform a saying "Just because you can write Ada in C, doesn't make C Ada".


Post Scriptum: one way to prove this is that we could use https://www.youtube.com/watch?v=43XaZEn2aLc as a base.

How to prove.

Assume that we add struct inheritance to Rust (it looks similar to JS) - call it RustInh.

Does adding inheritance to Rust increase expressive power? If yes, then right now Rust can't express OOP concept as inheritance. I.e. is there a C[RustInh] = C[Rust].

And way to prove it is to look at Deref anti-patterns https://github.com/rust-unofficial/patterns/blob/main/anti_p...

I'm still not sure that fully captures my intuition of easy to write.


> I'd consider something OOP if it has the same expressive power i.e. things expressible in OOP languages are easily expressible in another OOP language as well.

That doesn't even make sense on its face. There are things which are trivially expressible in Smalltalk and I'm not sure even possible to wrangle in Java.


That's because Smalltalk OOP isn't the OOP in the sense Java is, some refer to Java Style as Class oriented Programming.


That doesn’t make your original comment any less nonsensical, and still makes no sense: Smalltalk is very much class-based. Are you mixing up Smalltalk and Self?


To me, OOP always meant passing messages to objects, but it seems everyone has their own definition depending on experience.


> fragile base class problem

I do wonder whether this is as much of a problem as people think. Most of the time it only becomes a problem when people start doing stupid things to override the parent instead of refactoring a new class out from under it.


Having studied mostly OOP/Java at the uni, and been using mostly C# at work, I realized I might actually start to find programming fun again through Go. I've studied it a bit recently and this idea of interfaces with composition over inheritance etc. made somehow a lot more sense to me, and gave me this boost to try and learn it more because it felt so enjoyable. Not even with some practical problem at hand to solve, but just on an abstract level, not having to deal with OOP classes and inheritance and all the things. Now I just need to find a Go job and get my feet a little more wet. ;P I'm talking about Go because it gives me similar feelings as Rust, but Rust I have a lot less experience with. I hope there's a similar fun and excitement to be found in Rust as well, after unlearning some old habits.


Go is fun at first, and then it becomes soul sucking. It's all boiler plate. Many large scale projects have a lack of adequate unit testing, so large code bases are particularly painful to maintain. I attribute this lack of tests due to how the code needs to be structured, you have to needlessly add 'interfaces' throughout your code to accomplish things.

Go has it's strengths, but IMO fun isn't one of them. It turns into soul-suck quickly.


I'm in the same position as OP and was wondering the same thing; finding a job using Go. Is the bloat that you see the most related to error checking? If not what else is it?


The bloat is mostly related to mocking and making a fake client for everything for unit tests, and the practices you have to follow to make that actually work.

In Python or Java, you can do something like:

class: f1(): self.some_api_call() <process api call here>

test_class extends class some_api_call(): <mock definition>

In go, you cannot do this. Fair enough, but everyone writes object-style code because it's easier and there's less boiler plate. If you do this, you can't write tests.

Instead, you have to do something like:

class: apiMethod = nil f1(): self.apiMethod() <process api call>

And assign apiMethod when you create the instance of the object; there are no constructors because they're not traditional objects, you need to create a factor method (boiler plate) or build the struct by hand (boiler plate). None of this is terribly challenging in and of itself, but if you work on large code bases, nobody did this because it's extra work, and now you have to refactor it because you want to make a change to the critical business logic and prove you're not introducing a regression (a constraint the l33t coders before you didn't have because they get to go fast and break things).

And finally, often times the thing you need to mock is in someone else's package, and it doesn't implement an interface, or the thing you need to mutate is private, etc, etc. You end up need to mock half of a package sometimes.

None of this is insurmountable, but when you do it day in and day out for years like I did, it's a real slog, and it sucks your soul out of your body. It wasn't uncommon for me to spend literally 20x as long refactoring and implementing tests as it was to ship features. If you look at some unit tests from large go projects, you'll see stuff like struct{struct{struct{struct{...}}},struct{struct{struct{...}}}} because so much stuff needs to be mocked up. And since interfaces are disjointed from classes, you won't know with certainty which methods you need to needlessly mock for your mock class until you try to compile, because the interface is defined somewhere else and not attached to a base class, it's just a definition floating out there in the source somewhere.


I'm reposting your message with the code formatted as you had entered it (the original white space use is still visible in the html source code; I've just indented it so that HN recognizes it as preformatted):

---

The bloat is mostly related to mocking and making a fake client for everything for unit tests, and the practices you have to follow to make that actually work.

In Python or Java, you can do something like:

    class:
      f1():
        self.some_api_call()
        <process api call here>

    test_class extends class
      some_api_call():
        <mock definition>
In go, you cannot do this. Fair enough, but everyone writes object-style code because it's easier and there's less boiler plate. If you do this, you can't write tests.

Instead, you have to do something like:

    class:
      apiMethod = nil
      f1():
        self.apiMethod()
        <process api call>
And assign apiMethod when you create the instance of the object; there are no constructors because they're not traditional objects, you need to create a factor method (boiler plate) or build the struct by hand (boiler plate). None of this is terribly challenging in and of itself, but if you work on large code bases, nobody did this because it's extra work, and now you have to refactor it because you want to make a change to the critical business logic and prove you're not introducing a regression (a constraint the l33t coders before you didn't have because they get to go fast and break things).

And finally, often times the thing you need to mock is in someone else's package, and it doesn't implement an interface, or the thing you need to mutate is private, etc, etc. You end up need to mock half of a package sometimes.

None of this is insurmountable, but when you do it day in and day out for years like I did, it's a real slog, and it sucks your soul out of your body. It wasn't uncommon for me to spend literally 20x as long refactoring and implementing tests as it was to ship features. If you look at some unit tests from large go projects, you'll see stuff like struct{struct{struct{struct{...}}}, struct{struct{struct{...}}}} because so much stuff needs to be mocked up. And since interfaces are disjointed from classes, you won't know with certainty which methods you need to needlessly mock for your mock class until you try to compile, because the interface is defined somewhere else and not attached to a base class, it's just a definition floating out there in the source somewhere.


for me it's lack of map/reduce


You must learn to love the boiler plate, understand why doing the boiler plate well matters.


As someone who even structures their Python in OOP, I'd appreciate elaboration on how you organize your Rust code. I've been glancing at Rust for far too long, never finding a weekend to dive in. Save me some headaches, how should I approach e.g. Customer - Product - Order relations without OOP? What is the canonical "Rust Way"?


Your customer, product and order are still going to be objects, but they won’t reference each other directly with references/pointers. Instead they’ll either store an id, or you’ll have a central registry of relationships using some kind of id (could be a key into a hashmap or an index into a vector) and then fetch data from the central store (or have the caller pass it in) at the last minute when running code that does something.


> they won’t reference each other directly with references/pointers

I'm not sure if I understood your reply correctly, but there seems to be nothing in Rust stopping one dynamic trait (interface-based) object directly referencing another dynamic trait (interface-based) object. The main difference between C++ O-O and Rust trait-oriented programming is C++ inheritance of implementation vs. Rust inheritance of interfaces. You can also downcast in Rust, if you try hard enough.

https://gist.github.com/rust-play/fbe471b12a0fabe4ab7b653835...


You can do references in Rust. But you can only have one mutable reference to an object at once unless you work around this with locks (Mutex, RefCell, etc). So for anything other than trivial child object references, you can quickly paint yourself into a corner of compilation errors by using webs of references. And this seems to be the basis of a lot of people's frustrations with Rust.

It's true that you also can't do inheritance. But I've found this tends to be less of a problem as most OOP languages encourage composition over inheritance anyway, and composition works the same in Rust as in other languages.


I definitely see your point about the interconnection of references, mutability and lifetime in Rust, but I guess the issue of whether Rust has a problem with the classic O-O comes down to the question of what you are trying to implement. If you are implementing a graph-like data structure (for example, UI widgets in a window), you need to keep track of multiple mutable constituent structures; in that situation, Rust references indeed are problematic for the classic O-O and you need Rc<>/RefCell<> smart pointer shenanigans. If you are implementing an actor (an algorithm, a subsystem, a process, etc. - for example, a Product, a Customer and an Order), you usually need to keep track of only a handful of structures representing the input to an actor, all of them likely immutable (a mutable actor with its input kept immutable to eliminate unexpected input side-effects); in that situation, Rust has no problem with the classic O-O, since abstractions can be expressed through references to trait objects.


The nice thing about doing it this way is that it makes it much easier to serialize and/or save to a database.


The major gist with Rust is that you only build `structs` with the basic data, and very important, stick things together with `traits` and/or `enums`.

So, for my eCommerce app I have something like

  struct Line {
    order_id: usize,
    product_name: String
  }

  
  struct Order {
    order_id: usize,
    customer: Customer,
    lines: Vec<Lines>
  }
And this means instead of pointers, using ids (alike RDBMS) is much nicer overall (work wonders that the data is already like that in RDBMS!).

You can avoid ids and just embed the data too.

Following the idea of think like in databases, when you need to find fast bring a HashMap/BtreeMap:

  struct Line {
    order_id: usize,
    product_name: String
  }

  
  struct Order {
    order_id: usize,
    customer: Customer,
    lines: BtreeMap<usize, Lines> <-- BTree nice for ordered data and range queries!
  }


This sounds to me like the journey into functional programming. I've experienced the same thing myself.


I've encountered this pretty-much every time that I've learned a new language or framework. I start out trying to use it the way I've used things before, and then I slowly learn how the people who wrote it expect me to work, and then things get a lot easier.


That too and I don't think I'm even done with that, myself :)

What I meant is the step away from the notion that things are objects, stuff gets done with methods, methods are available via mixins/ inheritance/ magic, etc.

I've seen the notion of using values and pure functions as data oriented programming recently but never understood what that moniker adds.


I think that going with pure functions/functional programming simplifies things, because it makes it much easier to test that things work as you'd expect when you don't have side-effects/state to think about.


I had a very similar experience; thought it was an interesting language but I got discouraged around the time the small stuff I was working on required lifetimes, or had weird reference errors (or at least, what I thought was weird at the time).

When I tried again recently I started with the Too Many Linked Lists, which I felt did a better job of explaining both lifetimes and how the compiler views <type>, &<type>, and &ref <type> as completely different types, and that helped a ton.

Combine that with spending more time understanding how Rust handles object composition (compared to "traditional" OOP), the massive compiler improvements W/R/T lifetime elision and auto-derefing within the past several years, and realizing just how damn helpful the compiler and documentation is compared to other languages (again, thanks to TMLL for explicitly showing this), and it's been a downright pleasure to use this time around.


Indeed, I’m rereading The Rust Book , after struggling and finding slight success about around 2018, and am surprised at how much better the presentation is, as though they maintainers have closely studied where people got hung up, and then targeted those areas to add clarity.

I strongly suspect that it will stick this time.


However, if I'm not mistaken, the language still has no formal specification.


This is a somewhat heretical opinion but I don't think having a formal specification is particularly important for programming languages today.

It was very valuable, say, twenty years ago, when there were most programs were written or compiled using multiple closed source implementations of languages coming from competing companies. There were real economic incentives for the implementations to diverge from each other in ways that harmed the larger ecosystem. A formal spec was a forcing function to make those implementations compatible.

Today, programmers simply won't use a language whose implementation isn't open source with a very permissive license. This makes it very hard for an organization to deliberately make the implementation incompatible with others because other organizations and users are able to either avoid the incompatibility by forking the implementation if they don't like it, or making it compatible by using the implementation if they do.

I still think it's very valuable to have a committee with members of all implementations that helps drive consensus for where the language should go. But the document itself I see as secondary to that human process.


The value of a formal specification is the ability to reason about a program without having to compile and run it (using a specific implementation).


Good luck reasoning about C++ code. After all static analysis, sanitizers and whatever dynamic analysis, you still cannot be really sure that your code will blow up with undefined behavior.


A second implementation based on GCC is underway and will help press this very issue.


The person who wrote this blog is really good at C++ and compilers (along with many other things), so I think this would be relatively easier for him.


I love rust. I didn’t find it difficult to learn and at this point several others I’ve introduced it to also haven’t. I’m baffled as to where that reputation comes from.

I get that it makes you think about what you’re building a little more than something you can throw together like Python or Ruby but if the end goal is software that works correctly, getting there definitely isn’t harder with rust. It’s the complete opposite :/.


I use Rust for more or less everything these days, but I found the learning curve to be fairly tough up front. That was in 2019, though; maybe it's easier today with all the improved compiler diagnostics and new books and whatnot.

> if the end goal is software that works correctly, getting there definitely isn’t harder with rust. It’s the complete opposite :/.

Strongly agree with you here. It takes me a lot less time to put something that functions correctly together with Rust than it does with Python or Go. Maintenance also gets a lot easier as the codebase grows.

I recently started a job at a Python shop, and the kinds of bugs/regressions we hit are super annoying because a compiler with a type system would have had them simply be build errors up front.


> I’m baffled as to where that reputation comes from.

It's a deep language with a mix of borrowed syntax(es) and designs. You can pick up Go or Python or Nim in an hour and have a working application, that's fairly unlikely with Rust.

I'd say it took me a year to feel comfortable with Rust - to the point where I could just sit down and write applications without Googling every five minutes - which is far longer than I've spent with any other language.


This is good because no application of any import should be built in a language with which one has faux similarity.

A lot of really nasty problems are caused by people thinking they understand what JavaScript is doing.


Do you have a lot of experience with manual memory management? (Or are you just writing very simple 1-off programs?)

Rust definitely requires a different thinking process than most other languages. If you're already doing that kind of thinking, you'll probably have a very gentle learning curve.

On the other hand, the concept of ownership and borrowing takes a bit of time getting used to. Once ARC is in the picture; the curve gets very steep if you're coming from a language with automatic memory management where all state is mutable.


> I’m baffled as to where that reputation comes from.

Aside from the awesome community that rust has, don't underestimate the power of marketing. Someone should ask Mozilla how much was spent for rust development.

Sun microsystems spent about $500 million in 2003 to push forward Java. https://www.theregister.com/2003/06/09/sun_preps_500m_java_b...


I have been thinking to myself whether I should pick up Go or Rust as a new language this year.

Coming from a NodeJS background, Rust looks a tad more complicated but it looks cooler. There are also more job listings looking for Golang than Rust which makes me wonder if Golang might be a more rewarding investment?

What would be a good use case of Rust than Golang cannot do given its extra complexity and potentially lesser monetary reward? Any advice on which I should pick as a new language to learn?


Rust is the more elegant and powerful language. Creating a new language and repeating the "billion dollar mistake" by including null (sailing under the brand name "nil" in Go) is just crazy. Error handling is another strange thing in Go. And generics have been only introduced recently, but there is hardly any support for libraries in it (now). While Go is definitely fast enough for most scenarios, it is not the best language for low level code like drivers or kernel development.

So, depending on your goals, I think Rust is the better language in general. But if your goal is to get something done fast, then Go would probably be better, since it doesn't require that much learning effort.


> Creating a new language and repeating the "billion dollar mistake" by including null (sailing under the brand name "nil" in Go) is just crazy.

You forgot pointers used to represent nullables.


While Go is definitely fast enough for most scenarios, it is not the best language for low level code like drivers or kernel development.

Go was never ever intended for this purpose.


Yes, I know (although they promoted it as a "systems language", but it was not really defined what that should mean in the beginning), but it is a restriction, you don't have in Rust. Basically, Rust can do everything Go can do, but not the other way around. That _might_ help to make a decision for a language.


Technically, assembly can do everything Rust can do, yet that doesn't help to make a decision for a language. Ergonomics matter.


It seems to be an idiom shift. Systems means connected parts, go concurrency does just that, connecting parts through channels. But it's not `systems` as in bare metal electronic chips systems. More like IT `system`.


> Creating a new language and repeating the "billion dollar mistake" by including null (sailing under the brand name "nil" in Go) is just crazy.

Can you explain? What do I set ‘score’ to when someone hasn’t sat the test yet?


Read about sum types. They exist in Haskell, Rust, OCaml, Typescript, Swift, Kotlin, etc. You are likely only familiar with product types without knowing they're called product types. (Cartesian product)

You can have 100% type-safe, guaranteed at compile time code without null that can still represent the absence of data. Once you've used sum types, you feel clumsy when using Javascript, Python, Go, Ruby, C, C++, etc. especially when refactoring. Nullness infects your data model and always comes out of nowhere in production and ruins your day.


Arguably dynamic languages have sum types: every variable is one big sum type with the variants being every other type! I suspect the lack of sum types in many static languages are partially responsible for the popularity of dynamic ones.


> I suspect the lack of sum types in many static languages are partially responsible for the popularity of dynamic ones.

I can totally see this. I started writing a small cli tool in Go, and despite knowing way less Rust, I switched to it and was able to make a lot better progress at first due to pattern matching and result types. It was just so much easier/more ergonomic to write a simple parser.

The Go code was a mishmash of ugly structs and tons of null checking and special casing.


I would say that traits are a better analogy for dynamic types, but at the same time you can think of enums as closed sets and traits as open sets, so they are different ways of encoding sets of possible structure and functionality, more alike in what they provide than it initially seems.


Some statically typed languages have union types that are extensible in a similar fashion; e.g. in OCaml:

https://v2.ocaml.org/manual/polyvariant.html


Very interesting perspective!


To be fair to Python, the static typing doesn't have the billion dollar mistake (you have explicitly say `Optional[int]`, for eg).


https://old.lispcast.com/what-are-product-and-sum-types/

That was easy to understand.

> You can have 100% type-safe, guaranteed at compile time code without null that can still represent the absence of data.

If it's a single score, I'd still want to use null / int. There no invalid states being represented, anything else is still unnecessary complexity.

> Nullness infects your data model and always comes out of nowhere in production and ruins your day.

A TS example: trying to do anything with 'score' where score is `null | number`, would be caught be the compiler.


>If it's a single score, I'd still want to use null / int.

In your example, you still have to manually check if there's a value every time, but this is not compiler-enforced. Should you forget, you will get a runtime crash at some point (likely in production at a critical time) with some kind of arithmetic error. This wouldn't be possible with a simple sum type.

Also, a sum type with units of Score(Int) and NoScore won't allow assignments of any other "null" instances. Null in one spot is interchangeable with null in any other spot, and this can lead to bugs and runtime crashes. Null should be avoided when possible.


The compiler would enforce Number-ness every time I try and run a function that takes a number, right?

Wouldn’t I still have to check for NoScore?

> a sum type with units of Score(Int) and NoScore won't allow assignments of any other "null" instances.

I get this part - I wouldn’t be able to assign ‘NewBornBaby’ (my name null) to ‘NoScore’ (my score null)


> The compiler would enforce Number-ness every time I try and run a function that takes a number, right?

> Wouldn’t I still have to check for NoScore?

No, because you differentiate between the sum type (e.g. Maybe in Haskell) and the number type at compile time. It's a small distinction - there will still be one or two places where you ask "is this a Maybe-score, or a Just-Score, or a No-Score", but the upside is that in all places you are very clear if a No-Score is possible, and you can't confuse the value with the error signal.

I.e. if you pass maybe-scores to something that computes the mean, you'll get a compiler error. The writer of the mean function doesn't need to care you've overloaded an error value onto your numbers.

The compiler support is the important part. Languages like C/C++ know sum-types just fine. They usually surface as sentinel values (NULL for ptrs, -1 for ints, etc) or unions with type fields. The stdlib offers it as std::optional<T>. As you progress along that spectrum, you get increasing compiler support there, as well.

One could even argue that sentinel values are a slightly better choice than Go's pairs, because they are closer to being sum-types than the strict product type that is go's (result, error) tuple - at least sentinels can't be simultaneously carrying a valid value and an error.


> I.e. if you pass maybe-scores to something that computes the mean, you'll get a compiler error. The writer of the mean function doesn't need to care you've overloaded an error value onto your numbers.

If I pass a null into something that calculates an average, taking numbers, the TS compiler will complain now. The writer of mean() (assuming mean() is typed) doesn’t have to know anything about my code.


This only works for non-sentinels. I.e. if "-1" happens to be the value indicating NoScore, I don't think the TS compiler can catch that?


Nobody is discussing using a magic number for null, nor is that an accepted practice in TS.


>Wouldn’t I still have to check for NoScore?

That's right, and the compiler will reject programs where you don't do this. It's a set of safety rails for your code. You pay a dev-/compile-time cost in exchange for your programs not exploding at runtime.


Yes but that’s no less work than checking for null in TypeScript. The parent said:

> In your example, you still have to manually check if there's a value every time, but this is not compiler-enforced.


Sure, it's the same amount of work, but you're forced to do it and the compiler will be able to reject invalid programs. In languages that allow null (including TS, it doesn't 100% enforce the stuff that Haskell, etc. does), you can skip that check, saving some work I suppose, at the risk of your code exploding at runtime. Having stack traces which jump across 50,000 lines of code because someone forgot to check for null somewhere sucks a lot.


TS wouldn’t let you skip that check though.


The Billion Dollar Mistake refers to the fact that things that are not explicitly marked as "nullable" can be null/nil.

In rust, you would annotate score as `Option<u32>` (`u32` is one of Rust's integer types), and then you would set the score of someone who hasn't sat the test yet as `None`, and someone who got a 100 on the test as `Some(100)`.


Also, because Rust is intended for writing low level software where you might very well care deeply about how big this type is:

Rust has NonZero versions of the unsigned types, so NonZeroU32 is the same size as a u32, four bytes with an unsigned integer in it, except it is never zero.

Option<NonZeroU32> promises to be exactly the same size as u32 was. Rust calls the space left by the unused zero value a "niche" and that's the perfect size of niche for None.

As a result you get the same machine code you'd have for a "normal" 32-bit unsigned integer with zero used as a sentinel value, but because Rust knows None isn't an integer, when you mistakenly try to add None to sixteen in some code deep in the software having forgotten to check for the sentinel you get a compile error, not a mysterious bug report from a customer where somehow it got 16 which was supposed to be impossible.

When a maintenance programmer ten years later decides actually zero is a possible value for this parameter as well as "None", there's a NonZeroU32, they swap it for u32, and the program works just fine - but because there's no niche left in u32 the type is now bigger.


Oh yeah, allowing values to be nullable by default is bad, that's totally different than just 'including null'. I thought they meant including null in the language!

> you would set the score of someone who hasn't sat the test yet as `None`

Yep that's what I expected. Emoji thumbs up.


>Oh yeah, allowing values to be nullable by default is bad, that's totally different than just 'including null'.

In Rust (and Haskell and OCaml for that matter), there is no built-in null keyword. Option is just an enum in the library that happens to have a variant called None. So it's technically Option::None and Option::Some(x). But, really, it could be Quux and Quux::Bla and Quux::Boo(x) instead--without any language changes.

That is vastly better that what IntelliJ does for Java with their weird @NonNull annotations on references--which technically still can be the null. null is still a keyword there, and null is somehow a member of every reference type (but not of the other types--how arbitrary).

And C# has a null keyword, and the rules for type autoconverting it are complicated, and some things you just aren't allowed to do with null (even though they should be possible according to the rules) because then you'd see what mess they made there (you'd otherwise be able to figure out what the type of null is--and there's no "the" type there. null is basically still a member of every type. And that is bad).

So even the language used in "allowing values to be nullable by default" is insinuating a bad idea. Nullability is not necessarily a property that needs to exist on values in the first place (as far as the programming language is concerned).


For all practical purposes, in C# with nullability checks enabled, null is not a member of every type anymore: T? includes null, while T does not.


Rust has Option which you opt-into for cases like the one you describe.

What's special is that it's not like Go/Java/JS/etc where *every* pointer/reference can be null, so you have to constantly be on guard for it.

If I give you a Thing in Rust, you know it's a Thing and can use it as a Thing. If I give you an Option<Thing> then you know that it's either a Some or None.

If I give you a Thing in Go/Java/etc well, it could b nil. Java has Optionals...but even the Optional could be null...though it's a really really bad practice if it ever is...but it's technically *allowed* by the language. Rust doesn't allow such things and enforces it at the language and compiler level.


You can still use a null, however the point being made here is that null was failed to be included at type level.

See these for reference

* https://kotlinlang.org/docs/null-safety.html

* https://dart.dev/null-safety

* https://docs.microsoft.com/en-us/dotnet/csharp/nullable-refe...

An orthogonal approach would be using a data type such as Maybe or Option, but ergonomics depends on language.


I use Go at my job, and Rust in a few personal projects.

The "typical" Go nil-check would usually look something like this (no idea how code will look, apologies up front):

result, err := someFunction()

if err != nil { ...

It's nice that you're not having to litter your code with try/catch statements, or use a union type with an error value like in other languages, but the downside is that Go only checks to see whether err is used at some point (or makes you replace it with _), and it's possible to accidentally use err in a read-context and skip actually checking the value. Go won't prompt you that the error case is unhandled (in my experience)

In Rust, when you want to return a null-like value (None), you wrap it in Option<type>. To the compiler, Option<type> is a completely separate type, and it will not allow you to use it anywhere the interior type is expected until the option is unwrapped and the possible null value is handled. You'd do that like this:

var result = some_function()

match result {

  Some(x) => handle_value(x),

  None => handle_null(),
}

The compiler forces you to unwrap result into its two possible underlying types (any possible <type> or None), and handle each case, which prevents an accidental null value being passed to handle_value. Trying to pass result directly into handle_value would give you a type check error, since it's expecting a <type> but is passed an Option<type>. The compiler will also give you an error if you try to only handle the Some(x) path without providing a case for None as well, so you can't just accidentally forget to handle the null case.

(For completeness, you can also just do result.unwrap() to get the inner value and panic if it is None, which can be useful in some cases like when you know it will always be filled, and you want to fully terminate if it somehow isn't).

So in your case (assuming this is in the context of a video game), you'd make score an Option<i32> for example, then unwrap it when you needed the actual value. Generally speaking, I'd make the score returned from a saved game loading function be Option<i32> and make the actual score for the current session just an i32, then the function that handles loading a game save into the current session would handle the Option<i32> from the save file (defaulting to 0 when this is None), and we could assume that the score would be set by the time the game session is running so we don't have to constantly unwrap it within the game logic itself.


As someone with extensive experience with Rust and a teensy bit of experience in Go I can tell you that I adore Rust for every use case I’ve tried it out for *except* for network services. It works ok for low level proxies and stuff like that; but Python/Flask-level easy it is not.

Meanwhile my experience with Go has been the reverse. I’ve found it acceptable for most use cases, but for network services it really stands out. Goroutines <3


Yes. Rust is for what you'd otherwise have to write in C++. It's overkill for web services. You have to obsess over who owns what. The compiler will catch memory safety errors, but you still have to resolve them. It's quite possible to paint yourself into a corner and have to go back and redesign something. On the other hand, if you really need to coordinate many CPUs in a complicated way, Rust has decent facilities for that.

Goroutines don't have the restrictions of async that you must never block or spend too much time computing. Goroutines are preemptable. The language has memory-safe concurrency (except for maps, which is weird) and has garbage collection. So you can have parallelism without worrying too much about race conditions or leaks.

Go comes with very solid libraries for most server-side web things, because they're libraries Google uses internally. Rust crates have the usual open source problem - they get to 95%-99% debugged, but the unusual cases may not work right. Go has actual paid QA people.

Go has limited aims. It was created so that Google could write their internal server side stuff in something safer than C++ and faster than Python. It has a limited feature set. It's a practical tool.

I write hard stuff (a multi-thread metaverse viewer) in Rust, and easy web stuff (a data logger which updates a database) in Go. Use the right tool for the job.


> You have to obsess over who owns what.

Most of the time you can also avoid this by just copying data using .clone(). This adds a tiny bit of overhead, which is why it isn't a default - but it'll still be comparatively very efficient.

Similarly, there are facilities for shared mutation (Cell/RefCell) and for multiple owners extending the lifetime of a single piece of data (Rc/Arc). It's not that hard to assess where those might be needed, even in larger programs.


This big time. Rust has comparable ergonomics to high level languages once you stop trying to optimize everything and start throwing around clones liberally. And the nice thing is that if you do need to optimize something later you can trust the compiler that if it compiles, then it’s correct (barring interior mutability/unsafe).


> The language has memory-safe concurrency (except for maps, which is weird)...

My understanding is you should operate the other way around. Things aren't safe for concurrent mutation unless it's explicitly documented as safe.

> So you can have parallelism without worrying too much about race conditions or leaks.

You might not worry, but I find these the two easiest classes of Go bug to find when entering new codebases ;).

Still, I agree Go is easier to get a web service up and running with.


Regarding concurrency and Go, it's truly an awful language from the view of concurrency when coming from Rust. It's a loaded footgun where you have to keep tons implicit details in your mind to get it right.

https://eng.uber.com/data-race-patterns-in-go/


Minor nitpick but goroutines are absolutely not preemptable, they’re cooperative. The go compiler regularly sticks in yield statements around IO, and certain function calls, but you absolutely can starve the runtime by running a number of goroutines equal to the number of cores doing heavy computation, just like Node.JS’s event loop.


This changed in Go 1.14 on most platforms: https://go.dev/doc/go1.14#runtime


TIL


Noob question - What does go lack that makes it hard to write your multi threaded meta verse viewer


Any sort of safety: https://eng.uber.com/data-race-patterns-in-go/

Go's concurrency model is bog-standard shoot your foot off shared memory.

A channel is not magic, it's a multiple-producer multiple-consumer queue, you can unwittingly send a pointer over it (or a pointer-ish, like a hashmap) and boom you've got unchecked concurrent mutations, and in Go that even opens you up to data races (memory unsafety).

And because channels are slow, it's common to go down the stack, and hit further issues of mis-managing your mutexes or waitgroups, to say nothing of spawning a goroutine by closing over a mutable location.

You can do it right, in the same way you can do it right in C, C++, Java, Ruby, or Python. And arguably it's more difficult in Go because it drives you significantly more towards concurrency (and spawning tons of goroutines) without really giving you better tools to manage it (the only one I can think of is select).


Long time rust/go dev, go is so much faster to write for web services. For the rest rust is pretty nice.


> I adore Rust for every use case I’ve tried it out for except for network service

thats hilarious, my daily driver language is elixir which is the goat for network services. I saw rust as the perfect compliment for that where I need everything else.


Could you please expand on this a bit? What are some example services that would be better written in go vs rust?


I have no experience with Rust myself and a good 2-3 years with Go, so my opinion here is biased but: I think Go is more suitable for general all-purpose programming, whereas Rust is more specialized; I'd pick the latter if you need to do high-quality, close-to-the-metal software, and Go for more generic software, taking the same spot that NodeJS did for you.

That said, Go isn't a very "convenient" language; there's not as much magic or magic libraries that come with the language that take over big chunks of your application, and the community at large actually recommends against using libraries in favor of just using the standard library.

And after a while, I... kind of agree? My HTTP API is gorilla/mux, which is only a thin layer over the standard library's HTTP server APIs (it adds routing / path matching). I started using Ent to abstract away my database, then switched to Gorm when I got annoyed at having to write so much code just to do inserts / updates and manually having to add / remove rows, but that one falls apart as soon as you have a data structure that maps to more than one level of tables deep.

I mean part of my troubles with databases is probably a disconnect between wanting to store items like whole documents but mapping them onto a relational database.


You might look into your database's support for JSON columns. Sometimes you can even make indices for expressions on them


sqlc is very go-like, i recommend it.


What are you looking for in the 2 languages? Here are some of my thoughts:

Golang

* Development speed: Golang wins by far. It's closer to Python in that regard, but with strong typing.

* Very nice multi-threading via Go routines and channels. Impressive semantics in simple syntax, requiring no synchronization mechanism on the user side.

* Large garbage collector penalty.

Rust

* Complete language with build system, crate, docs hosting.

* A lot more performance compared to Golang, on par with C++.

* Doctests (!).

* Slow to learn, slow to compile (probably not a deal-breaker, especially if you focus on microservices).

Concerning what would be a good usecase for Rust vs Golang, check this out:

https://discord.com/blog/why-discord-is-switching-from-go-to...


> Very nice multi-threading via Go routines and channels. Impressive semantics in simple syntax, requiring no synchronization mechanism on the user side.

Except for all the times they are required and Go doesn’t tell you: https://eng.uber.com/data-race-patterns-in-go/

Go’s fundamental semantics are standard not-safe shared-memory multi threading. It provides an mpmc queue as a built-in out of necessity (since no generics originally, which would have made for an awkward situation), but really the only thing that’s notable about it is `select`, not `go` or `chan`.

In in some ways `go` is even a downgrade from other languages: you can’t get a handle on the goroutine and thus have to muck around with side-channels to even know that a subroutine has terminated let alone get its result.


>In in some ways `go` is even a downgrade from other languages: you can’t get a handle on the goroutine and thus have to muck around with side-channels to even know that a subroutine has terminated let alone get its result.

You sound like you don't know why they explicitly refuse to add the ability to get a handle on the goroutine. If you do know, you are misleading people by omitting it.


My thoughts on your thoughts:

* Go also includes the complete build system, package management (although it wasn't there from the beginning), code documentation, testing and fuzzing. Also the standard library is much more extensive than in Rust ("batteries included").

* Doctests: since you mention them explicitly, Go has those too: https://go.dev/blog/examples

* "Large garbage collector penalty": that obviously depends on your application and what you consider acceptable. I would say that in most cases the easier development enabled by having GC is worth it, but YMMV. Here's a discussion on the Go garbage collector: https://news.ycombinator.com/item?id=12042302


Wow, very nice! I didn't know that. I love how the language is evolving, it also now has generics.


> slow to compile

In my experience it's the slowest language to compile ever, and the binaries generated are gargantuan.

I was super excited to learn Rust, but that excitement is now 100% gone.


Binary size very much depends on your settings. Rust does not optimise for a small 'hello world' by default, but it's very possible to get small binaries from it if that's a priority for you (for example, the default debug build neither optimizes for size nor strips debug information, as well as statically linking the standard library). Compile times are more sticky problem and most ways to improve it basically involve avoiding certain language features and libraries.


I wonder what other languages you have experience with. C++ is obviously not one of them.


Same. I was about to jump into learning Rust and make it my no.1 language for new projects instead of node.js + TypeScript, but once I learned about the slow compile times I stopped that thought immediately.


For me it was a choice between slow compile times now when it’s safe, or unbounded time solving security and stability issues later when it’s critical.


How "large" is the garbage collector penalty? My understanding is that the Go GC is significantly more efficient than for example JS's mark/sweep approach. Apples to oranges for sure, but I am interested in better understanding how expensive the Go GC is vs Rust performance.


Check out the discord article I posted in the original reply.

If I interpret the graphs correctly, I see:

Golang:

* baseline 20% cpu + spikes to 35% once the GC runs.

* response times of about 1ms + spikes up to 10ms.

Rust:

* baseline 12% cpu + flat, no spiking.

* response times of 20us + flat, no spiking (!).

In terms of scaling, I interpret the results in favor of Rust.

My reasoning is the more you run the GC, the bigger the penalty.


The question here is how much can you disambiguate between the impact of GC and the impact of differently written code plus differently optimizing compiler backend (where Go is still very simple but Rust uses LLVM). If for example the Go code used goroutines and channels in those places where the rewritten Rust code uses asynchronous operations, that alone may account for substantial differences in performance.


Posting links to his blog post is misleading in 2022. The issue they mention in their blog was fixed so it's not an issue now.


That's interesting. Would like to read about the solution more. Do you have a reference?


Just read that article; very informative.

The TLDR is that, especially during GC, P99s and response times are notably worse in Go than Rust. Makes sense.


Go’s GC is not efficient, it is responsive. That is, it trades throughput (and performances, and efficiency) for small pauses and concurrency.


I had the same reflexion about 2 years ago. Realistically, pretty much any program written in NodeJS can be ported to Go and vice versa. But not every Rust program can be ported to NodeJS/Go. It opens up a new class of software that is not typically available to NodeJS/Go developers (e.g. systems programming, embedded, writing libraries for other languages, etc.).

So I went with Rust and am very happy with the decision. Despite being a systems programming language, it feels surprisingly like a high-level language and I now default to programming stuff in Rust unless there's a strong reason to use NodeJS.

PS: That said, an often understated benefit of Go is that the community and job opportunities are massive in comparison to Rust (for now, at least).


I founf myself in your same situation a few months ago. I chose Rust and regret it

Rust is a better c++. It's not appropriate for anything you wouldn't program in c++. The coding speed is slow

So if you are thinking about learning a new language to program your future apps you currently code in node, choose Go


> Rust is a better c++

The Rust language is a far better C++.

In practice, the compile time and binary size of Rust are out of control, which makes Rust far from being a slam dunk over C++. Crossing my fingers this will change!


I get the sentiment, but like to nuance it a bit.

The compile time story of rust and C++ is comparable, if you use it the same way. If you use deep include hierarchies and no precompiled headers, if you start using template heavy code like boost, or if you do code generation, the C++ compile times will quickly spiral out of control. Rust does not have the include problem, but the specialization/templating idea is shared with C++, and the code generation problem has an equivalent in the macro mechanism as used by e.g. serde.

In theory, you can get comparable compile times from both. Main difference is rust heavily emphasizes a programming strategy that depends on these 2, and it has a heavy cost in compile time. Meanwhile, a lot of the C++ code is still in the 'C-with-classes-and-every-vendor-creates-a-string-class' camp and while the abstraction level is lower, so is the compile time.

For the binary size, C++ can hide a lot of stuff in libraries, while rust compiles it in and duplicates it for each executable. So you pay a higher fixed cost per application. As long as rust has no stable ABI, rust will stay behind at this point. But it gets better optimization opportunities as it can merge the standard library code with yours, and inter library calls are more costly so runs wins there too. Presumably it's early day for a stable ABI in rust, big wins are still made. Long term, I'd personally like to see some ABI stability in the rust world.


Rust has a stable ABI actually, it's called the C ABI. But since most Rust crates do rely on monomorphized generic code in many ways, it's just not possible to share binary artifacts in the first place. (The situation is similar for "header only" libraries in C++.) If you're writing a library that truly does not involve generic code, it makes total sense to expose it via a pure C interface anyway so that languages other than Rust can make use of it.


C ABI is very well supported indeed. After commiting some contortions to avoid varargs, I found out Rust does actually cover them.

But it's still something different than a real rust ABI. Basic things like pushing a string or an enum to a client will hurt. Providing a rust library without source is not an option for now.


As of Rust 1.62, compilation time is no longer a valid criticism. Things have improved so much in the past 2-3 versions, it's no longer slower than C++. Sorry!


>> As of Rust 1.62, compilation time is no longer a valid criticism. Things have improved so much in the past 2-3 versions, it's no longer slower than C++. Sorry!

Is there a report or study somewhere that shows how much it has improved?

I had heard improving compilation and build time was being worked on, but I don't get to use Rust much in my current work.

I would be interested to see how much improvement has been made and in what areas.



There are a few things to make things slow still. Try macros generating async code. That can go out of control and is a common case in tests.


One drawback of Go (in my opinion) is that it has a runtime. So it's very difficult (impossible) to use it with other languages that also have a runtime. So if you learn Go, you'll never be able to use it to interoperate with e.g. your Python program to speed it up.

With Rust, you could use it to replace the most time critical parts of your high-level program piece by piece. The learning curve is then much easier, and adoption can be gradual.


> So if you learn Go, you'll never be able to use it to interoperate with e.g. your Python program to speed it up.

Never done it myself, but:

https://www.ardanlabs.com/blog/2020/07/extending-python-with...

https://github.com/go-python/gopy


Having a runtime does not, by itself, preclude interoperating with other languages that have their own runtime. Here's a project that does that for .NET and Python:

http://pythonnet.github.io/

(Note that this is not a reimplementation of Python on top of CLR, but rather a bridge between CLR and CPython.)

The thing that makes FFI problematic in Go is green threads, which have their own stacks that nothing but Go understands. Thus, every FFI call has to arrange things to be what the callee expects, and you can't do async using goroutines across language boundaries (whereas callback-based solutions like promises work jsut fine).


Learning Rust is a good exercise in understanding memory safety. Learning it will make you understand the gotchas of what can go wrong with memory allocation, which will translate very well to coding in other languages, especially C.

Just like learning Haskel is a good exercise in understanding functional programming and lazy evaluation, which again will translate very well to the code you write as you will be able to identify pattens where functional programming can be applied.

However, neither language is really super applicable to general development, because there are hoops you have to jump through to write basic code where the language advantages are not really need, and other languages are much simpler and faster to develop in.

Golang is much more widely used for this reason, as its compiled yet features a gc, simpler to develop in with java contains a lot of good functionality in its stdlib.


Since you're coming from a NodeJS background, you'll want to pick up an introductory textbook about C as well. Rust implicitly relies quite closely on the C machine model, and introductory books about Rust (such as "The Rust Programming Language") don't do a very good job of conveying the nitty-gritty details of that model to novice coders. This is a pretty nasty pitfall when trying to code in Rust, and it's important to address it early.


I'm self-publishing "Rust From the Ground Up" which takes this approach: each chapter rewrites a classic Unix utility (head, cat, wc, ...) from the original BSD C source into Rust. I find for systems programming it's easier to understand how things work in C, and then teach the idiomatic way of doing it in Rust. C is pretty easy to follow along in "read-only" mode with some explanations.

https://rftgu.rs/


Interesting, seems similar to the book Command Line Rust which also teaches you by reimplementing Unix commands, any thoughts on the differences?


Yes I saw that... I released mine first but I'm publishing it a chapter at a time and mine is about half done. I haven't read the O'Reilly one because I don't want to inadvertently copy anything or be influenced by it. I think the main difference between my book and this one is that I go through the original BSD source and translate it into idiomatic Rust. I also teach how to work with the borrow checker without resorting to copy/clone or reference counting which I think is unique. And I don't use lifetimes anywhere in the book - they're an advanced topic that really puts off new Rust programmers and aren't needed in most cases.


Nice, is the BSD C source unchanged in the book?


Yes! I use the (final) 4.4 release from 1993. Some were written by Bill Joy! But if you look at the modern OpenBSD or FreeBSD (or MacOS) versions they’re still about 90% unchanged. The older versions are a bit shorter which makes it easier to explain.


I second this.

I've learned Rust after having used Python, and at first I had a few "wtf" moments, things I just couldn't understand.

Everything fell into place after discussing these with a friend who knows much more about the "nitty-gritty" than I do.


Reading an entire book on C is probably overkill. This video is probably enough for most beginners: https://www.youtube.com/watch?v=rDoqT-a6UFg


Wow thank's for that link. Bridged a few gaps for me between what I remember from c++ and what I've been learning in rustlings.


This is the main reason I’d recommend that a Node programmer learn Go first.

I haven’t learned Rust yet, thought I’m quite keen to do so, but I have an (ancient) background in C, C++ and 8-bit assembly. I don’t find that I really use much of that knowledge when I work in Go, even though Go is still a lot closer to the metal than Node.


> What would be a good use case of Rust than Golang cannot do given its extra complexity and potentially lesser monetary reward?

Anything where low-level control is required. It's not clear if there are true-Rust web apps in the wild (as opposed to web apps with some services in Rust); as far as I read, Rust web programming is ugly.

The market still offers few positions, largely dominated by crypto. I have the impression that it will still take many years before the field will move to Rust (where appropriate).

> Any advice on which I should pick as a new language to learn?

Depends on the concrete goals. If you want to make a career, Golang is the safe bet, by a very long stretch. If you want to have fun, try both, and you'll naturally find your inclination, as they have a radically different flavor.


> Anything where low-level control is required. It's not clear if there are true-Rust web apps in the wild (as opposed to web apps with some services in Rust); as far as I read, Rust web programming is ugly.

There are. I run one. Written in pure Rust. 160k lines of Rust code in total, serving over 3 million HTTP requests per month. Best choice I've ever made.

Rust especially shines when you need to maintain a big project over a long period of time. People often say that Go is much more productive than Rust, and in most cases that is true, but as the size of your project increases and your expertise in the language increases this relationship inverts, and Rust starts to be much more productive. Pick a right tool for the job.


What Rust web libraries/frameworks do you use and recommend? How long does your Rust project take to compile?


> What Rust web libraries/frameworks do you use and recommend?

This might not be a satisfying answer for you, but I use my own framework. Its main selling point is that it can be compiled in two modes: during development it has zero dependencies and is blazingly fast to compile (it has its own minimal HTTP implementation, its own minimal executor, etc.), and for production it switches to use production-ready crates which everyone else uses (`hyper`, `tokio`, etc.)

Personally I'm not a fan of most frameworks which are commonly used in Rust webdev. The reason for that is twofold:

1) Most of them suffer from what I call the npm-syndrome, with hundreds of dependencies out of the box. Case in point, the minimal example from Warp's readme pulls in 146 crates.

2) They're often really complicated and have a lot of magic. I prefer simple functions with explicit control flow instead of layers upon layers of generics and macros stacked upon each other.

(DISCLAIMER: The following is purely my personal opinion; I'm not saying one approach is objectively better than the other, just stating what I prefer.)

For example, in Warp the way you add compression is by stacking a filter on top of your route:

    let examples = warp::path("ex")
        .and(warp::fs::dir("./examples/"))
        .with(warp::compression::deflate());
In my framework you have an explicit function through which every request goes through, so if want to add compression you just call a function which takes a request and returns it:

    fn deflate(response: Response) -> Response { ... }

    async fn server_main(request: Request) -> Response {
        let response = ...;
        let response = deflate(response);
        //
        return response;
    }
There's no magic and you can clearly see exactly what's going on, and you can also easily add new transformations without the need to become a trait astronaut.


Amazing! Thanks for explaining that.

I think that's a great solution because the biggest thing that makes me afraid of Rust webdev is compile times. Your solution seems perfect as you can get quick compile times during development.


Anywhere you picked this up from I could read more about?

Fellow npm-syndrome avoider.


I'm not the OP, but I liked Warp[0] the most. Actix Web is cool, but looks ugly and hard to use. Rocket doesn't even work. And Tide is lean and clean, but its examples and codes aren't as well-made as Warp.

[0]: https://github.com/seanmonstar/warp


Have you tried axum? It's by the Tokio organization, newer than the others.

I still use actix-web though since I found it way easier to parse than axum or warp, in my opinion. The minimal examples for each make actix-web the most readable for me.


For probably 99% of cases, Go will be a better fit as it is noticeably easier to learn and will require less time to do the task.

Unless you need those extra nanoseconds of performance or super low-level features, Go will be a much better choice.

In the end, they are just tools, and you need to choose them based on your needs, not language features.


I'd say it's also a question of team size and organization. Are you a singular developer, or in a large team, writing enterprise software that should be easy to pick up for someone reading the code 10 years later etc.


I personally feel rust is advertised more as safer and faster rather than as a practical yet harder alternative to C or C++. Although Go as a systems programming language is misleading, it's not something that is as heavily discussed about.

In the long run Rust's complexity will hurt newcomers(new to programming) while it will be a blessing for seasoned c and c++ devs. If all programming languages were tools, rust would be a very very specific tool which makes a lot of sense for a specific case. If nodejs and golang are tools, choosing one over another is easier as you can do same things in both easily with small effort. But you cannot rewrite all rust programs in nodejs or golang.

Finally you need to ask if rust is really worth picking over golang/nodejs for things that can be easily done in nodejs/golang. Rust is not for people who think is rust for them.

Arguments like some implementations are more elegant in some other language can always be brought up as arguments. They should only be taken into account when you run out of options to compare because they are exaggerated and subjective most of the times. For example(exaggerated) screaming why go doesn't have a borrower checker like rust makes no sense because go is garbage collected. For many people seeing such absence of features equate to lack of features in a programming language leading to more boilerplate or other downsides which is not necessarily true.


I enjoy writing go a lot more then I enjoy rust. Rust is... dense. It's a lot harder for me to read code and understand what it's doing.

That said, I feel that Rust is likely the winner long term. So I'm still building my rust skills, but programming personal stuff in go.


>That said, I feel that Rust is likely the winner long term.

Interesting; why do you think so?


I think Go would typically be considered a possible substitute of one the "more VMy" languages, Rust a complement.

At first glance this looks as if Rust would be more at home with polyglotism, but the typical Rust complement would be a hot performance critical part of something much bigger, and this is already deep in the realm of strategical commitment (all the not so hot parts have to buy in). Whereas Go often enters the picture in isolated one-shots and can grow from there.


If you want to optimize your learning experience, then learn Go first. Rust has a very steep learning curve, whereas you can pick up Go very very quickly.

I learned Go by reading The Go Programming Language. It's a bit old, but a very good book nonetheless.


I second this. I'm a huge Rust fan, and not really a Go fan, but if you're thinking about leaning both, it will be much faster to learn Go first. Spend a couple weeks on that, and then you'll have another data point to compare while you learn Rust. (Go also distinguishes between pointers and values, which will be a helpful concept to have under your belt when you start Rust.)


If you learn Rust and become proficienct, Go will be comparably trivial.


For someone writing node apps, Go is a better fit. Go is much simpler than Typescript and almost performs as well as Rust. The reality is most Rust apps written by average Rust programmers do not noticeably outperform Go apps. Getting Rust to outperform Go requires a high level of proficiency in Rust. The effort vs reward definitely favors Go for node type of apps.


If you're looking for a new language in order to get a job, then I wouldn't be looking for either of these languages and look into something much more common like C++ given your background.

I see Rust as more for people in systems programming who want improvements over C, rather than a desktop developer looking to learn something new. There's a lot of hurdles that you only really appreciate if you've come from an unsafe/low-level background


> Coming from a NodeJS background, Rust looks a tad more complicated but it looks cooler.

Here, Rust has officially climbed past NodeJS on the "cool" ladder. Let's rejoice, and welcome our fellow programmers into our communities !


Most of my experience is with python and js. I feel like if you have experience with python, go resembles a lot. While if you have some experience with js, rust feels like an extension (but takes time to feel comfortable). If you like arrow functions, using map, filter, etc. You will feel amazing when using rust. Those are my 2 cents.

And spite of some other comments, I found writing web servers in rust okay, and I think go is also fine for web services. And with go it also feels a bit like python when using other libraries, I don't know where to start. While rust with cargo is more similar to the npm experience


I figured I should have given some context to my question on deciding to learn either Rust or Go.

One of the reasons I started thinking which language to pick is when I started diving into web3 development. It seems like there is a trend into either using Go or Rust or both in some of the ecosystems. Think Tendermint, Cosmwasm, Solana, etc. While it makes sense to just learn both languages, I don’t think I have the mental capacity to learn both together quickly. It might work better to learn the one that has the most potential in the long run based on the trend.


I worked in Haskell for many years. It is an absolute myth that rare languages command less salary. Remember the more popular the language, the more supply, hence less pay.


Just thinking out loud, does go compile to WASM? I often see performance critical WASM snippets written in rust, but never in Go.


Go compiles to WASM but the stock compiler is pretty bad for this and your binaries are like 2MB+ in size.

TinyGo is an LLVM based compiler that targets microcontrollers but also has a WASM target and that creates considerably smaller binaries, but it doesn't fully support all of the Go standard library.


TinyGo is great, I used to to program microcontrollers and there wasn’t anything I really missed, though admittedly it was a very small program.


Yes it does, but there are some issues, mainly the size of the compiled WASM file. But you can get around that by using TinyGo. I'm not that deep into it, but this article seems to give a good (and as far as I can see up to date) overview: https://elewis.dev/are-we-wasm-yet-part-1


Go does compile to wasm, however because wasm is what it is it has to carry around the entire runtime. This is obviously a much bigger issue for delivery over the web than it is for shuffling binaries around, or even more so creating them locally: last I’d checked, the baseline (an empty main function) for a go wasm package is about 1.3M.


Hardly an issue when looking at the ads crap that get download in every "modern" site.


Sure, but I think if you see the size of ad-loaded pages as a size budget or standard for your website you're already on the path towards setting your own bad example.


Sure if your use case for go wasm is ads crap knock yourself out.


That wasn't my point, ads and SPA crap available almost in every "modern" site, are several MB bigger in size than Go runtime on WASM.

Ergo a a WASM Go application will be much smaller than those "modern" sites.

I bet even smaller than GMail and Google Docs.


Sure it can: https://golangbot.com/webassembly-using-go/

Never tried it, though :)


I think it's worth spending a few weekends with Rust, even if it's just to expose you to some of the concepts in the language.

Beyond things like borrow-checking; the language introduces a LOT of concepts that are quite foreign. Exposing yourself to them is good; because I expect that future languages will borrow heavily from Rust.


you'll grow more as a developer from rust. Rust will force you to think about things like memory management, safety etc. additionally, it has a rich set of abstractions. rust also has better interop with c and c++. In the long run, you're better off learning rust. You'll encounter a brutal learning curve but once you know it, you'll find yourself able to do very powerful things with ease. especially once you internalize the borrow checker.

Go is a great language for the short term but I don't feel like it brings Anything unique or interesting to the profession. its only real advantage is that its quick to learn but there isn't much substance. Its a great language if you're the type of person who doesn't mind copying large reams of code to change a few lines for a new purpose.


There was a relevant discussion here just a little while ago: https://news.ycombinator.com/item?id=31976407


> What would be a good use case of Rust than Golang

If you can afford GC in your project go for Golang else Rust.


I think rust is the new haskell. After spending 7 months learning it, I can say I really enjoy the language, but there's no job in it and in my opinion, they take academic decisions that make the language way more complex than it should. Also, the community is toxic.

For example, generics in Go were criticized by some, praised by others.

You have the feeling that you can freely share your opinion in the go community without the risk of being harassed by the rest of the community.

In the rust community, just like a sect, everybody must say that everything is just perfect.

Passive / aggressive attitude is something I've seen a lot in the rust community.

I would suggest that if your plan is to learn C/C++ next, and you never really understood memory issues && pointers, then rust is a perfect choice at first.

I'm planning to learn Go next, I don't regret learning rust, I learned lots of things with it.


> but there's no job in

That's just not true.

Rust got already adopted by lot of either big or interesting to work at players (Amazon, Microsoft, DropBox, ...?) and, while anecdotal, I myself get also paid to program rust.

> the community is toxic. > In the rust community, just like a sect, everybody must say that everything is just perfect.

I often get the opposite feeling with all the diverse and lengthy discussions about how to do things, e.g., like getting async stable a few years ago or long blog articles of core contributors that state "we can do a lot better here and there" in public blog posts that get shared on /r/rust and don't get shunned.


> Rust got already adopted by lot of either big or interesting to work at players (Amazon, Microsoft, DropBox, ...?) and, while anecdotal, I myself get also paid to program rust.

There are very few open positions, though. Just check out the Rust newsletter - the open positions for each release can be counted on one hand.

I have the suspicions that Rust positions are typically filled in-house. Shopify, for example, adopted Rust, but AFAIK they did not hire anybody external to do so (nothing wrong with this, of course).


> Just check out the Rust newsletter - the open positions for each release can be counted on one hand.

IMO there's only a tiny selection on that newsletter, it's just not a canonical source for rust jobs. E.g. and again anecdotal, we don't post our rust job openings there either as such more widely read publications are seldom a good fit, just like the monthly HN "who's hiring" thread - we don't want a flood of applications whom a good percentage of cannot even bother to read the few basic "must haves" for the job.

Also, in house fillings need employees that can program in rust too.


Our company (Prisma) is currently hiring Rust engineers. Remote jobs, office in Berlin for those who need it and interesting problems to solve.


> Also, the community is toxic.

How so? I've seen the random drama crop up here and there, but it always seemed to mostly be about the "political" side of things, as opposed to the technical development.

What few interactions I've had with library maintainers and the tokio project have always been positive, and the people always seemed helpful.


Comments like this one below are often downvoted without reply just because they criticise rust:

> I founf myself in your same situation a few months ago. I chose Rust and regret it...

https://news.ycombinator.com/item?id=32105336

GP puts it well:

> Just like in any sect. As long as you agree everything is perfect, the community is the friendliest indeed.

The community isn't toxic as long as you happen to agree with it and praise Rust... Go figure.


GP is also wrong.

At this point it's mostly a meme. Rust is slow to compile. I mean, yeah if you abuse meta programming or monomorphisation.

I've been programming in it, and while I like the language, I'm not on the language community bandwagon. E.g. CoC and it's enforcement (I think it's just pointless grandstanding).


I've seen pretty much zero grandstanding in the Rust community about the CoC. They seem to treat it as any CoC should be treated in practice; a failsafe measure which is most critical for in-person events, and to protect people who make their real-world identity public from aggressive trolling and malicious behavior.


First. You are arguing against my sidenote, on why I don't consider myself Rust coolaid member. Second: it's just my opinion.

Third: I said CoC is grandstanding. It's just a toothless document, that pretends to solve issues much like renaming Git default branch master->main.

We saw it wasn't enforceable last year when Rust moderators quit over being unable to enforce it.


> Also, the community is toxic.

The Rust community has been the friendliest PL community I've seen so far.


There's similar thing about Haskell. I often hear stories like "wow, I'm so glad I found Haskell, the community has been so friendly and helpful" and then other stories like "I tried Haskell and I liked the language but it was just such a toxic community". I don't really know what to make of this, and at this point I just put it down to the infinite variety in human experiences and preferences.


Just like in any sect. As long as you agree everything is perfect, the community is the friendliest indeed.


Nope, many times I submitted a question, got help instantly with a very long explanation telling why the thing I try to do makes sense and should work, but some things in the compiler or libraries haven't been stabilized or finished yet, and they were working on that. And then giving me some temporary workarounds.

The community definitely acknowledges the limitations and sharp edges and listens to the users. Thanks to that attitude, Rust is way more friendly and easier to use than it was 5 years ago.


That's not fair to Rust. I think it's feature of community size and genuine interest/innovation in Rust.


> there's no job in [Rust]

As of today, indeed.com lists 1,500 remote jobs mentioning Rust vs. 4,018 jobs mentioning Golang. That's not so bad. (However, there's no way to tell how many of those listings are Rust-specific as opposed to polyglot job descriptions.)

> Also, the [Rust] community is toxic.

Speaking slightly humorously, if you think Rust community is toxic, try expressing your dissatisfaction with Swift or Apple in the fanboi Swift community (controlled by Apple employees) and see what happens to you :).


Most "jobs mentioning Rust" are in the crypto space for some reason. I can't fault devs for thinking that such "opportunities" are just not very serious compared to the alternatives.


Every one of the big tech companies have large Rust projects. Even Apple has open job listings for Rust devs.


I agree that crypto indeed started out as a dumb, speculative, monetary technology, but it is currently moving toward smart-contract, non-speculative applications, such as guaranteed voting or transfer-of-ownership systems. Crypto indeed started out as a nonsensically power-hungry technology (via Proof-of-Work), but it is currently moving toward power-friendly infrastructure (via Proof-of-Stake). I think the crypto sector will slowly become more and more interesting, employment-wise. For example, how about writing a single contract that is understandable not only by a machine, but also, at the same time, by a judge in a court of law?


Development is development.

Video games are not "serious", yet any C++ developer getting their chops in video game programming is not shunned for it.


> Also, the community is toxic.

I think "somewhat dogmatic" would be a more accurate description of the Rust community, IMO.

For example, I recently had a discussion about `enum` being a poor choice for what Rust makes it to mean from a C/C++ developer's point of view (which are Rust's main "replacement target" languages). The closest I got someone to agreeing with me is a sentiment along these lines "well, `variant` would may have been a better choice but it's not very familiar to C/C++ developers and, besides, when this decision was made, Rust had a hard limit of 5 characters on keyword length".


FWIW, changing the keyword and nomenclature for such a fundamental part of the language after so much time has passed is... not realistic. Any conversation around it is bound to be somewhat fraught, and happens often enough that people have gone over it and aren't particularly keen to retread it. To the person starting the conversation it might seem like people are handwaving away arguments or being dismissive.


> Also, the community is toxic.

I've found the complete opposite, Rust communities are the least hostile programming environments I've come across.

There's also a huge amount of irony about saying a community is toxic on this site


This is wild to me as someone who spent many years in Haskell and made shit tons of money doing it. There are myriad Haskell jobs available. I'm taking a break right now (mainly to pick up other skills), but would go back to it in a heartbeat. There are lots of Haskell jobs.


I've spent some time casually learning Rust in the past few weeks, and I'd really like to start incorporating it into my serious C++ projects. Rust itself is nice, but having a reliable package manager & build system in the form of cargo is absolutely the killer feature for me.

If I could use Cargo/cxx_build as my C++ build system (which I think may be possible, but I haven't seen a good example), I would fully embrace rust & start incorporating it into my professional work today.


I just want to give a shout out to "Rust for Rustaceans".

I'm only three chapters in, and it's definitely the most enjoyable technical reading I've ever done because you learn so much, so quickly, and so easily. Steve Klabnik (co-author of "The Rust Programming Language") says it's the book to read after going through "The Rust Programming Language", and I couldn't agree more.


+1 from me here! The book arrived yesterday (yes, I'm a luddite like that), and now I'm at chapter 8. For me, I'm already aware in one way or another of most things in it, but my knowledge is spotty in places, and all over the place too. So, R4R seems really great to put some framework around it, as well as get at least accustomed to the least familiar bits.

I would add though that I can see how it can be a pretty challenging read depending on prior experiences. Even then I believe it makes a lot of sense to read through it to at least get exposed to some topics and be able to recall later that you've read something about it when you need it.


The author fairly regularly live streams on youtube doing more advanced Rust programming, and is responsive to questions coming in.


To build on this, his Crust of Rust series videos are excellent deep dives targeted at the same stage of learning as the book.


I just finished this book. I liked it and learnt a lot, but in some chapters I felt way too far from my comfort zone. The unsafe chapter and some part under the hood of async like Pin/Unpin was difficult for me.


Thanks for the heads up! This is indeed worth noting.

Even in the first three chapters for example, if someone isn't familiar with memory layout in C, or the stack/heap distinction, ..etc, it can seem a bit complicated I think.


I am currently starting with rust too. I'm coming from a C#/ASP.NET/Angular/SQL background. I really like the official documentation so far. https://doc.rust-lang.org/book/ch00-00-introduction.html

I also really like this quick introduction. https://fasterthanli.me/articles/a-half-hour-to-learn-rust

You won't "learn" rust by reading it but you get a pretty good picture of the basics in my opinion.

Maybe someone more experienced can rate these as i don't have comparisons yet.


I liked that fasterthanlime article a lot too. The main thing it gave me was enough context to get enthusiastic enough about the language to sit down and read through the O'Reilly book¹ (which I thought was excellent).

I'm still very much a Rust beginner, but I've managed to build a couple of useful tiny projects and to hack a feature I wanted into someone else's big 'ol codebase!

¹https://www.oreilly.com/library/view/programming-rust-2nd/97...


One great thing about the Rust books and tutorial -- you can easily enjoy them offline, right out-of-the-box.

I learned the basics of Rust on a long plane ride with no WiFi. What a great way to pass the time!


This sounded interesting but I was confused by what you meant by "the book." Looks like that's how it's commonly referred to https://www.rust-lang.org/learn


This is the "The Rust Book": https://doc.rust-lang.org/book/ Affectionally known as "the book"


There's a new design patterns "book" out and it's pretty nifty as well.


oh! link?


sorry just saw this, I don't check comments but every couple of days https://rust-unofficial.github.io/patterns/ . you probably already found it on google though lol


I don't see anything explicitly calling out what to download in nonethewiser's link. How did you end up grabbing these things for offline learning?


There’s a “READ THE BOOk!” link to https://doc.rust-lang.org/book/. That page says:

“The HTML format is available online at https://doc.rust-lang.org/stable/book/ and offline with installations of Rust made with rustup; run rustup docs --book to open“

More importantly, it has a “next page” arrow at the bottom right, and a TOC pop-up at the top left of the page.


It's automatically available if you installed with rustup! Try this:

rustup docs --book


I use devdocs.io, but I think you can simply download the book using cargo/rustup ...


> Programming Rust (link to review) is the first book I read for an initial introduction to the language.

This book is very much under-rated. If you've unsuccessfuly tried to learn Rust by reading "the Book" cover to cover, you might try reading Programing Rust in the same way.


I don't personally have any experience with Rust, but I've been using Go for a good two to three years, so my opinion is biased. However, I believe that Go is better suited for general all-purpose programming, whereas Rust is more focused. I would choose Rust if you need to create high-quality, close-to-the-metal software, and use Go for more generic software, filling the role that NodeJS filled for you.



I was introduced to rust with https://github.com/LukeMathWalker/build-your-own-jira-with-r... at a local rust meetup. I think it's pretty good.


His book Zero to Production in Rust is also very good.


as an embedded c/c++ developer I tried Rust briefly a few times over the years, don't feel it's the right fit, due to the way it's static build by default the binary size is just too large, plus the library pool is still much small, and there is no equivalent to c++'s STL for speedy coding when I need it.

yes I can trick for size etc, but overall it just did not fit well so far, for the embedded space that is, but, 'system-programming language' has many use cases in embedded field.


But that's just the default, you can tell it to use dynamic linking. What libraries were you missing? I find the rust standard library is better in many ways, and it doesn't have the same performance footguns.


I don't feel Rust has any performance advantage to c and c++ per my own tests.

yes I can dynamically link to stdlib in Rust but I don't think Rust has a versioned dynamic library released for multiple architectures(still many embedded archs are not fully supported in Rust), that leads to problems in the field at depolyment and upgrade phases. Plus the dynamic library after strip is still close to 6MB, I can have a full libstdc++ around 2MB for the embedded system(with musl it is about 1MB), for many low-range embedded systems(there are a _lot_ of them), 6MB is still quite large.


6MB is quite large. Makes me question if you were even building in release mode.

You may want to see https://github.com/johnthagen/min-sized-rust

It's not difficult to get binaries down into the 50KB range. And for embedded applications, less than 10KB is totally possible.


Yes I tried all those 'minimize rust' approaches including the above one.

they worked fine if you're statically link to its stdlib.

but if you have a few complex rust binaries, static link for each of them is not going to help on the overall combined size.

I did use a released library and build everything for release(per those minimize projects), I can easily cut a small program from 3M to 290KB but again, it is either static link, or you need a 6MB dynamic library to go with it.

the key question is, what's the smallest size for Rust shared library? so far my finding is around 6MB(after strip, otherwise it's about 11~12MB)


Oh so your problem is that when attempting to dynamically link to the standard library, you're missing out on the dead code elimination you'd get when statically linking. Rust doesn't have a stable ABI anyways, so you can't really share the standard library between programs unless you're really careful.


you nailed it.

neither LTO nor panic=abort can be used at dynamic link, no stable ABI is another thing.

to me it means Rust is not a good fit for embedded space for majority of the use cases, sadly.


I'm not sure what embedded space you're referring to, but microcontrollers, in my experience, generally use monolithic binaries. I'm unsure why you'd wish to use dynamic linking in those cases.


What in the c++ STL in particular? Rusts iterator traits (or others) subsume almost every c++ algorithm I could think of.


Serious question: can I just use smart pointer like feature of Rc/Arc combing with Mutex or RWLock instead of fighting borrow checker? I know this practice is shunned upon by Rust purists but I like to see how practical it is in real life and project. Especially around using Rust as a safer but more performant language instead of pushing for ultimate zero-cost abstraction and top notch performance.


Although I can't recall them off the top of my head, several guests on the Rustacean Station podcast have suggested exactly that when one is getting their feet wet with Rust (put .clone() everywhere, put Arc<Mutex<..>> everywhere, ..etc).

The idea is that once you are familiar with other aspects of the language (syntax, sum types, traits, modules), you can go back and try to master the borrow checker.


Probably, yes. But note that ref counters will leak memory in the presence of cycles.

You need to either ensure that your pointer graphs contain no cycles (in which case you could probably also model it using ownership) or have some strategy for discovering and breaking those cycles (in which case ref counting isn't buying you that much).

In other words, if your data is naturally modeled using ref counts, then, yes, Rc is great. If not, it's not. And it's probably worth noting that most managed languages don't use ref counting, so the set of problems that are naturally modeled that way appears to be relatively small compared to tracing GC.


The main drawback of trying to do this might be that the syntax is unwieldy. If you have one big shared object, or shared objects only in a specific part of your program, it's no big deal, and it arguably helps call attention to what's going on. But if everything is using Arc<RwLock<...>>, you'll have .write().unwrap() or similar on every line, and it'll feel terrible.

The sibling comment mentioned cycle leaks, and in addition to that I'll add the runtime panics or deadlocks associated with locking the same thing twice.


Agree that I should not just stick Rc everywhere. The use of borrow checkers makes a lot of sense at a macro scale, i.e: libraries, APIs. But, I’m just wondering the justification for using Rc in a smaller scope, say inside my own implementation of a trait. If I am to provide external APIs then yes, sticking Rc everywhere would not be a good idea ergonomically.


I say give it a shot and see how it goes :)

One high level problem with learning Rust is that a lot of the patterns we use to work with the borrow checker are pretty non-obvious. (Things like using indexes instead of references, and avoiding methods that keep &self borrowed.) To the extend that Rc and Arc let you avoid learning those patterns, I agree with folks who say not to use them too much. But if you know how you might solve something without Rc, and you want to try it with Rc anyway to see how it feels, that's totally reasonable.


In addition to munificient's response, I'd point out that this will likely have a significant performance cost. Since performance is one of the main reasons of using a low-level language like Rust, it should give you pause.

I'm not saying there's anything bad with Arc + Mutex - they're certainly useful tools and great in some scenarios; it's just that I wouldn't reach out to them just to "avoid fighting the borrow checker". If your code can be written without those tools, it's best to do so.


Refcounting had been a very common way to implement resource cleanup for object graphs back in 90s already, in a much more resource-constrained environment. E.g. COM, which is widespread in Windows to this day, is all refcounted.


A bit unrelated:

Maybe I'm wrong, but I get the impression that the people who learn Rust, tend to be quite experienced programmers. I have yet to see complete beginners document their journey, starting with Rust.

I've seen lots of people do that with C/C++, Java, and the other usual suspects - but Rust still seems like a language for at least intermediate programmers.


I think you're right but that it's not necessarily a bad thing. Rust's truly killer features probably won't appeal to you on a visceral level until you've been on the wrong end of a thorny problem like a race condition. Otherwise you might think it's a needlessly strict C++ clone.


There are definitely easier languages to learn for complete beginners. When someone needs to learn what a "string" is, it's not helpful to immediately require them to also learn the difference between a "heap-allocated string" and a "borrowed string".

However, you don't need to be an expert to learn Rust. It even helps if you don't have too many expectations about OOP and pointers, because their Rust equivalents have different design patterns/idioms, and you may need to unlearn some things.


> it's not helpful to immediately require them to also learn the difference between a "heap-allocated string" and a "borrowed string".

That's largely a special case of the general difference between owned and referenced data, which applies to all non-trivial data types. "String" just happens to be the first of those that most novice coders will encounter. If anything, it makes a nice "toy" case for the general distinction.


It's a very new language (NLL, a key feature in Rust ergonomics, was introduced in late 2018) and people are yet to figure out how to teach it to complete novices. Existing introductions assume that you do have some programming experience.


I’m on a similar track. Web/java background and started doing some tutorials and aoc, currently working on the the same ray tracer challenge book (it’s awesome). Not sure what a next step would be, maybe that one book where you implement a web browser or some raspberry pi project. Any recommendations?


See also https://loige.co/where-to-go-to-learn-rust-in-2021/ (books, video courses, newsletters, podcasts, blogs, workshops, etc)


Perhaps someone finds this useful: https://learning-rust.github.io/


You forgot to mention prior experience, which languages if any you have under your belt.


This is explained in much detail in the very first link, in the very first sentence of the post :-)


Apologies, I'm guilty of skimming thru. Thanks for clarifying.


“How” is the stupidest word to auto-censor. Removing it completely changes the meaning of titles. It makes articles seem vapid or banal when they wouldn’t otherwise, and vice versa.


It changes the meaning of some titles. It makes more of them less baity.

The danger with these arguments is that you only notice the cases that don't work. That's a 100% failure rate! Can't get more stupid than that!

pg suggested this edit to me years ago and I spent a long time back testing it on old titles. At the time, it was a clear win. I suppose it's possible that title conventions have changed since then and taking another look might be a good idea.


> The danger with these arguments is that you only notice the cases that don't work. That's a 100% failure rate! Can't get more stupid than that!

I’m gonna go out on a limb and suggest maybe you’re too close to this. I notice the “clear win” cases frequently too. At best they’re very clearly and awkwardly editorialized. I click on them frequently just to reassure myself that I’m aware I’m not mistaking reality for fiction. I do the same with “why” and “\d” editorialized titles that get auto-edits. Maybe the clear win is identifying clickbait, but the editing is definitely not effective.


What's "\d" in this context?


Digits. Regex syntax, sorry about that.


Ah, thanks!


In this case, it changes the title so much that it's not correct English anymore.


It could suggest a travelling rust learner in the same way that swordsmen in feudal Japan used to perfect their art by travelling around.


Ha! That's funny. I hadn't thought of that.

I may be mistaken, but I think to be correct in that context, it would need a comma:

"I went about, learning Rust"


The comma is not necessary for it to be correct, but with the comma the meaning is a bit different still.


It is correct English without "How" but the meaning is different and the usage is not typical.


An exclamation at the end would fix this, but yeah, I'm of the opinion that censoring the 'How' didn't distract from the article until I saw this thread.


Holly molly. I was like, that’s a weird title.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: