11 ms·
I recently gave a Rust workshop to Kotlin and Swift developers. Strings in Rust are a really, really difficult topic for complete newcomers because they're unde
by terhechte 4y ago
I recently gave a Rust workshop to Kotlin and Swift developers. Strings in Rust are a really, really difficult topic for complete newcomers because they're understood as a basic type whereas in Rust they require having read half the Rust book to grasp.
Consider: I can teach a lot of Rust basic with `usize`. Defining funcions, calling functions, enums because they're `Copy` and because there's only one type. String requires knowing about &str which requires knowing about deref which requires knowing about (&String -> &str), it also requires understanding lifetimes, moving, heap and stack, cloning. Then, if you want to work with the file system you also need to understand Paths, OsString and AsRef.
With Kotlin and Swift, for all these things, you really just need one type, String, and you handle it just like usize.
It is really a bid of a hurdle for new developers coming from higher level languages (especially if they just give it a quick try).
- nicoburns 4y agoOn the plus side, String makes a really good example to explain ownership, moving, stack vs heap, etc. All of which you need at least a basic understanding of to do anything non-trivial in Rust. I kind of feel like it goes without saying that Rust isn't ideal for beginners. For developers who already have a good knowledge of other languages I feel like learning about these things shouldn't be a problem, as becoming familiar with these concepts is one of the main benefits of learning Rust.
- smaddox 4y ago> I kind of feel like it goes without saying that Rust isn't ideal for beginners. I think that depends on, first, what the goal is, and second, what you're comparing to. It think Rust is easier on beginners, in many ways, than C. And C is easier on beginners, in many ways, than assembly or machine code. But if you want to really understand computer programming, starting at machine code or at least assembly isn't a crazy way to start.
- tialaramex 4y agoBeginning with machine code for some simple architecture (maybe RISC-V these days?) might be one good route in. I can also see (having experienced it myself, albeit I already knew C etc. these were not requirements and many of my classmates did not) beginning with a pure functional language where all the practicalities are abstracted entirely. Today the University where I learned this begins with Java, which I am confident is the wrong choice, but the person who part-designed their curriculum, and is a friend, disagrees with me and he's the one getting paid to teach them.
- nicoburns 4y ago> It think Rust is easier on beginners, in many ways, than C. And C is easier on beginners, in many ways, than assembly or machine code. But if you want to really understand computer programming, starting at machine code or at least assembly isn't a crazy way to start. I mean sure. But equally, starting with Python isn't a crazy way to start. And Python is much easier language to learn than any of those (esp. if you want to actually create something practical with it).
- hgomersall 4y agoSure, but if your objective is systems programming, you'll probably quickly get to the point of realising python is not the right choice.
- pjmlp 4y agoDepends, if writing a compiler is still considered systems programming in modern times. https://www.amazon.com/Writing-Interpreters-Compilers-Raspberry-Python/dp/1977509207 https://www.amazon.com/Writing-Interpreters-Compilers-Raspbe...
- less_less 4y agoCompilers are their own beast — I wouldn't put them with systems code. They're pretty different from an OS, BLAS, machine learning kernel, game engine, network stack, database or what have you. There's not as much buffer management, speed and memory aren't usually as critical, you don't make direct syscalls, many structures are graphs rather than arrays, etc. They often aren't even multithreaded. It's also popular to write compilers in distinctly non-"systems-y" languages, most notably Standard ML but also eg Haskell, and lots of languages are self-hosted.
- bsder 4y ago> I kind of feel like it goes without saying that Rust isn't ideal for beginners. I agree if you mean: "Beginners should be using Python, Ruby, Java, etc." People who suggest that "Beginners should be using C." have blocked out from their memory the trauma that is "Segmentation Fault (core dumped)."
- dralley 4y agoCounterpoint: Beginners should use C just long enough to be traumatized by "Segmentation Fault (core dumped).", so that they can appreciate the affordances that higher level languages offer (but hopefully not long enough that they run away from the whole subject)
- jollybean 4y ago'Becoming familiar with Rust, is one of the main benefits of learning Rust' The problem is that we seem to believe there is something 'inherent' about the way Rust solves these problems, and that by doing so with Rust lifetimes, we solve some kind of inherent issue in software. This makes us believe that Rust = Value in some ways which I believe is wrong. Rust is only 'one way' - and it may be far more complicated than it needs to be and in many cases more complicated than it is worth. I feel that Rust may be an experiment, our 'first attempt' at advanced compile time safety, like it's a kind of C++ whereas the Java/C# of memory safety will happen once we've ingested all of these lessons. Funny I feel that with Rust 'There's a much smaller and clearer language struggling to come out' like what Stroustrup said about C++.
- hedora 4y agoComing from a C++ background makes me think Rust is the smaller and clearer language that came out of C++. The only successful C++ code bases I've worked on were multithreaded, and most were also asynchronous. If you assume those design decisions are required, then you'll find that programmers have to reinvent the borrow checker in their head and manually apply it to code in whatever language they're using. That's incredibly boring and tedious, and people are demonstrably terrible at doing it. Rust automates away all that stuff. I don't know of any approach to threading or I/O that doesn't rely heavily on reasoning about ownership and mutability. In that sense there really is only one way to do it.
- eru 4y ago> I don't know of any approach to threading or I/O that doesn't rely heavily on reasoning about ownership and mutability. In that sense there really is only one way to do it. Erlang's message passing style is interesting here. And so is eg Software Transaction Memory that you can try in Haskell. Have a look at deterministic parallelism, too.
- jcelerier 4y ago> Erlang's message passing style is interesting here. it just hides who owns the message data object though, no ? The problem is still here. e.g. for instance I work on a C++ software where there is a lot of message passing across threads, but the OS thread on which "malloc" and "free" are called for the messages and any data they may contain really, really matters - some threads must never call "malloc" or "free" under any circumstance. Would I be able to manage this with Erlang, knowing that every thread should be able to send message to pretty much any other ? Also, looking at that code... https://www.erlang.org/blog/message-passing/ https://www.erlang.org/blog/message-passing/ I don't really see what this gains me over the trivial C++ solution: processing_thread.message_queue.send([=] (Context& ctx) { int x = ... some computation ... ctx.ui_thread.message_queue.send([x] { ... show some info ... }); }); which can become even simpler with coroutines in C++20 if one does not like callback style
- thejosh 4y agoRust is a hard language to learn, but once I got over the initial hump of learning all the things that don't exist in higher languages/that I've forgotten about it's a great language. The best thing I did was start doing minor contributions to opensource Rust libraries, I did a small PR for arrow2 and polars which taught me a lot, and also the maintainers and contributors there are incredibly friendly and helpful.
- klabb3 4y agoDon't worry. As soon as you explain to them that appending to a PathBuf is O(1) amortized they'll come around, and it will scale much better for all their GB-sized file paths. I guess this adds a prerequisite on complexity theory but nobody should go anywhere near advanced data structures like strings with less than a bachelor in CS.
- lijogdfljk 4y agoMakes me wonder if there could be room for a SimpleString library. I love/use Rust. I don't think any of this is complicated. BUT, i'm a big fan of just "clone your problems away" for beginner Rust users. Going knee deep into techniques which merely reduce memory usage when people likely don't actually care - at all - about it just feels wrong to me. So yea, maybe a cursed library where SimpleString is just some niceties around some Cow + Arc thing which is also Copy. Hell, you could probably just apply it Vec and who knows what else. Anyway, clearly not something i'm advocating anyone _really_ use. But it seems a nice way to make stuff "Just Work" in the beginning.
- codedokode 4y agoBut Rust is designed to write high-performance code. If you don't care about performace, you don't really need Rust. Swift or Go seem more readable and easier to use.
- astrange 4y agoMaking memory management obnoxious by not handling copying for you doesn't really encourage performance. It just encourages people to prematurely optimize things that don't matter.
- howinteresting 4y agoSwift is well-designed but is virtually non-existent outside of Apple platforms, so it doesn't have nearly the third-party ecosystem that Rust does. Go has the third-party ecosystem but is poorly designed and doesn't have basic language features like sum types. Rust is likely the best combination of thought-out design and ecosystem support that exists in a programming language today.
- pjmlp 4y agoRust is also pretty much focused on Linux workloads, mostly. Also the Apple ecosystem has plenty of third parties, including commercial libraries.
- tialaramex 4y agoI think I'd recommend teaching Move semantics not Copy semantics from the outset, because Move semantics work fine everywhere in Rust and the Copy semantics are just an optimisation. As you've found, if you teach Copy then for types which aren't Copy you now need to teach Move. Languages like Kotlin and Swift are doing a lot of lifting to deliver this behaviour for String, and of course they can't keep it up, so students who've done more than a little Kotlin or Swift will be aware of the idea of "reference semantics" in those languages where most of the objects they use do not have the behaviour they've seen in String which is instead pretending to be a value type like an integer. Again, if you only teach Move, you're fine. After not very long a student will wonder how they can duplicate things (since they didn't know Copy), and you can show them Clone. Clone works everywhere. Is cloning a usize idiomatic Rust? No it is not. Does it work just fine anyway? Of course it does! And of course Clone is implemented for String, and for most types beginners will ever see.
- hgomersall 4y agoAre copy semantics always used in place of move semantics for a Copy type? I didn't know that.
- deleted 4y ago[deleted]
- tialaramex 4y agoLiterally all that Copy does is it says after assignment the moved-from variable can still be used. So in this sense, sure, these semantics are "always used". But if you don't use the variable after assigning from it, you could also say the semantics aren't used in this case. Does that help? Copy does a lot less than many people think it does. If you're a low level person it's apparent this is because Copy types are just some bits and their meaning is literally in those bits, Copy the bits and you've copied the meaning. Thus, this "it still works after assignment" Copy behaviour is just how things would work naturally for such types. But Rust doesn't require programmers (and especially beginners) to grok that. It's possible to explain Copy semantics first in a way that's easier to grasp for people coming from, say, Java, but that's only half the picture because your students will soon need Move semantics which are different. Thus I recommend instead explaining Move semantics from the outset (which will be harder) and only introducing Copy as an optimisation. I think this might even be better for students coming from C++, because C++ move semantics are a horrible mess, so underscoring that Move is the default in Rust and it's fine to think of every assignment as Move in Rust will avoid them getting the idea that there must be secret magic somewhere, there isn't, C++ hacked these semantics in to a finished language which didn't previously have Move and that's why it's a mess. I'm less sure for people coming from low-level C. I can imagine if you're going to work with no_std on bare metal you might actually do just fine working almost entirely with Copy types and you probably need actual bona fide pointers (not just references) and so you end up needing to know what's "really" going on anyway. If you're no_std you don't have a String type anyway, nor do you have Box, and thus you can't write Box<str> either, although &str still works fine if you've burned some strings into your firmware or whatever.
- lumost 4y agoRust strings are difficult for others coming from statically typed and low level languages as well. It’s one of the types programmers will most often encounter, and yet it’s one of the most obtuse topics within rust.
- k__ 4y agoI remember strings being "not so easy" in C/C++ too.
- oconnor663 4y agoI think the big differences are that copying and reference taking are automatic and invisible in C++. So a lot of APIs taking string or string& will "just work" for the beginners, and you can delay the part where you talk about how different those things are. This sounds like a minor difference, but I've met lots of developers who do meaningful work in C++ but who don't know what a copy constructor is. I get the impression that there's an enormous difference between being a C++ "user" vs a "library writer", because there's so much automatic stuff happing under the covers. Rust tends to have a bit less invisible complexity, I think, but some of that difference is just making the complexity visible (like reference taking), which effectively frontloads it onto beginners. It's a tough tradeoff.
- rocqua 4y agoI occasionally port my C code to C++ just for more ergonomic strings.
- jokethrowaway 4y agoAfter haskell strings, rust strings actually felt reasonable
- eru 4y agoAlas, Haskell's built-in strings are horrible. Anything serious would use Data.Text or Data.Bytestring (depending on the situation). Unfortunately, most of the language and libraries got written before reasonable strings were available. So you still see lots of things taking linked-list strings.
- agumonkey 4y agorust has one uphill battle in the mainstream adoption is that a lot of things make sense if you wrote bare metal code. If not then it can be very confusing.
- brundolf 4y agoI wrote a blog post that tries to bridge this gap: https://www.brandons.me/blog/why-rust-strings-seem-hard https://www.brandons.me/blog/why-rust-strings-seem-hard
- astonex 4y agoI remember trying out some Rust years ago, I stopped after how difficult Strings vs str vs &str was. I think if I tried again today I could probably grasp it now I have a better understanding of concepts like string views, and encodings.
- Ericson2314 4y agoIt's not good for beginners to get weird errors for file paths which aren't valid Unicode.
- mlindner 4y agoSince when would a beginner be hitting file paths that aren't valid Unicode? The only systems that aren't Unicode are old legacy systems. (Or is even now Windows still broken?)
- burntsushi 4y agoNeither Linux nor Windows require their file paths to be in any particular encoding. (With some restrictions, like no interior NUL bytes, among others.)
- mlindner 4y agoI don't understand people's trouble with Strings. They're not any more difficult than what's in C++ and they have a lot fewer footguns comparatively.
- mytherin 4y agoThat's just not true. In C++ you can use std::string everywhere and everything will mostly "just work". In Rust there are 5+ string types that are each subtly different, support subtly different operations and cannot be used interchangeably. The standard library will hand you different types of strings depending on which functions you are calling, so you cannot avoid learning about all of these types either. Now in many ways the Rust design is nicer for low-level programming, since it allows you to avoid making copies all over the place, and has certain added safety benefits around e.g. unicode validation. But it is certainly way more difficult to use than std::string. When I was learning Rust it certainly took me a while to wrap my head around all of these string types and their various limitations - and I have a low-level programming background and understand exactly why all of these string types exist and why these limitations are there. I have to imagine this will be a major road-block to learning Rust for people from e.g. a Python or Javascript background who are not familiar with all these low-level details.
- afdbcreid 4y agoIt is more difficult for beginners. Experienced C++ programmers also manage their strings carefully. Rust just makes it explicit, which is a trade off it chooses in lots of cases (the most famous being ownership). It is a trade-off: it makes it harder for beginners but easier to use correctly.
- xarope 4y agoI've not delved into Rust much, but strings in any language are "hard", because underlying the representation is an array/list/heap/vector/whatever, which means thread safety/garbage collection/cache coherency issues, etc. Just that some languages make it "easy" until the footgun, whereas it seems like Rust presents the problems right at the start?