Panicking is a perfectly good way to handle the situation where an invariant of the program is violated. That is, it is perfectly fine to index using [] in cases where there cannot possibly be an out-of-bound access unless the program has a logic bug. And if it does have a bug, there's usually not much that you can do except abort because you can't trust the program anymore.
On the other hand, if you know that your input might be OOB without violating a precondition, as a part of the normal execution of the program, and you can handle it gracefully, use `get` instead.
This distinction between "expected" and "unexpected" errors is an extremely valuable one, and one unnecessarily muddled by languages where all errors are signaled with exceptions. Rust gets it right.
It's strange to me because a lot of what I value in Rust is writing code that handles all outcomes safely and with confidence. Yet that all seems to be out the window if you access array values - as it may or may not work.
The safety of [] slice/array access is entirely up to how the dev wrote the code.. which largely feels the same as any other language.
Am I thinking about something incorrectly?
With code that indexes slices, the index computation is usually much more proximate to the actual indexing operation. If ensuring the validity of the index is about upholding an invariant internal to the module, it makes no sense to return an "sorry, I have a bug" error to the caller if that invariant fails to hold. What would the caller do about that?
If you use Rust 2018 now, non-lexical lifetimes are a bigger recent improvement: https://doc.rust-lang.org/nightly/edition-guide/rust-2018/ow...
For Programmers that try Rust out Rust can be hard because they are stubornly trying to program Rust as if it were Python/Java/C/C++/Foo – but it isn't.
Rust as a language makes certain types of approaches (or anti patterns) nearly impossible. In the beginning it happens quite often that after a day of struggle you will erase a Rube-Goldberg-Machine that does something simple and replace it with the right line of code that does exactly the same while beeing way more extensible.
If you find yourself building more and more elaborate structures just to get something very simple done, the problem is very likely your approach (e.g. trying to implement a OOP structure instead of solving the problem) – at least that is what happend to me a lot.
Rust's borrowing and ownership concepts, as well as traits and generics make it extremely powerful, but these concepts lend themselves better to certain ways of structuring code than to others. Learning something is in my opinion always about allowing something to change your perspective. If you are not allowing the thing you are learning to change you, chances are that you are not learning, but judging.
(Also I have to rant here a bit: Yes I know the GC you used in Java or Emacs in 1997 was terrible, but modern GCs are very good indeed)
It would simply iterate through every entry in the temp string stack and then all the entries in the symbol table to find the string with the lowest address above whatever was the previous lowest address. After that full sweep, it would move that string to its new home and adjust pointers to it, then set the new low bound to the string just identified. It would keep iterating until all strings had been visited.
As a result, it required (n2)/2 sweeps of memory (n=# of strings). Having BASIC lock up for 30 seconds every once in awhile was just how it went.
[ and in case someone is going to correct me, it might have swept from top of memory to bottom, I have forgotten the details ]
I think you could use Rc<Thing> in rust too if you want to solve it using rust, but then you have "swift with more boilerplate" as one friend said.
Another actually more serious problem is that typical ref counting implementations mix the allocated data with the references, so that when you inc/dec the reference you dirty a cache line containing the data. There exist implementations which improve on this by storing the references in a separate part of memory, but they are not how it's commonly implemented.
Disclaimers: I'm only lightly familiar with Rust, and linked-lists are perfectly reasonable solutions _in the proper context and paradigm_.
So the problem to be solved is that we want to a) have collection of items which have a very strict ordering; b) be able to directly access the first and last item in that order; c) given a particular item, find the item which is immediately before/after it; d) when removing or adding an item, the relative ordering of the other items is undisturbed. Oh yeah, and e) we want it simple and efficient.
Doubly-linked lists are, of course, a very common solution used for these kinds of problems. They are perfectly suitable _if our paradigm is_ "I hereby assert that somehow, external to the compiler, I've verified that all list manipulation is being done correctly in all circumstances." If, however, our paradigm is "I want the compiler to automatically guarentee lots of these invariants" we run into some problems:
1) The data structure itself (each item has two pointers, and there are two global pointers for "head" and "tail") makes very few guarentees. Just one, actually: "each pointer will either point to an object, or will be null (or its moral equivalent)". There just isn't much compile-time meat for the compiler to chew on.
2) Linked-lists typically have (relatively) persistent scope, and exist outside of the lexical scope of whatever code block is immediately operating upon them. Again, it doesn't give the compiler much to go on.
3) Without managed memory (GC), there's no way for the compiler to guarentee that a pointed-to object still exists.
4) There's no built-in guarentee that the "head" and "tail" pointers actually point to the first/last object.
5) There's no guarentee of overall ordering (if a.next == b, then b.prev == a).
6) There's no guarentee of even a consistent view of the items in the collection (head == a, a.next == b, but b.prev == null/end-of-list).
7) There's no way for the compiler to guarentee, when viewing the collection as a whole, that modifications are atomic or thread-safe.
Yes, it's possible to take care of these issues in a non-Rust paradigm by making the structure of the list itself opaque, and only exposing insert/remove functions which enforce all the invariants. (I think Rust itself can do this with unsafe code.) However, you're still left with difficulties:
8) If list items are opaque containers, how do you guarentee that the payload object continues to exist?
9) How do you guarentee payload object ownership, atomicity, mutability, or other properties?
10) How do you guarentee that these needed invariants are held transitively?
Now the Rust paradigm, of course, isn't the only way to deal with these issues. But whether you're using Rust, managed-memory, C++ smart pointers, immutability guarentees, etc., you're going to need to do things that are unnatural in the other paradigms. Rust has the benefit of automatically enforcing lots of these invariants without having to do copying, worrying about shallow vs. deep copying, transitivey, etc.
When a C++ programmer starts with python they will write incedibly “unpythonic” code, when they do the same thing in Rust, it just won’t compile.
IMO Rust is very straightforward and the std library is incredibly good. But you have to really understand certain core concepts and what they mean in terms of structuring your code.
Just like that C++ programmer needed to learn what is ideomatic python looks like and why it makes a lot more sense to write python that way, you need to find the rust way of things. But this is the same for every progtamming language out there..
One of the reasons why when I learn a new language I deliberately research the idiomatic way to write that language, it saves a bunch of trying to hammer a square peg into a round hole time.
Also some languages purely because of the domain they are in can do things in ways that others can't (trivially) do so if you treat each language as "foo" with different syntax you end up hobbling yourself to the lowest common denominator of "foo"'s features across every language you use.
I had a great time writing Go web services, and I definitely agree that it was easy to pick up, both for me and other teammates. Figuring out the idiomatic way to do things wasn't always straightforward though, although getting up and running was easy.
One way is to just keep at it until it clicks. An other way is to not do that, but once you've got the words for something you start seeing the issue everywhere, and next time around it makes a lot more sense.
It has been my experience that if I try to learn something hard and give up and then come back to it a few months later, I have a way easier time even with no apparent changes in process. I suspect some of it is giving your brain time to process fundamentals or something.
I wonder if this is similar to C++'s `[]` vs `at`. `at` does implicit bounds checking but, as an optimization, if you are already doing an explicit bounds check, you can elide the implicit check via `[]`.
You can catch a panic, and you can compile C++ with -fno-exception. at() is not closer to get() than to [].
Not doing so means ending up with situations like the Linux kernel, Windows or console games, which might be ok, when code portability doesn't matter to start with.
Weirdly enough these things should also be a hard problem in other non-Garbagecollected languages, but you get away with a lot more when the compiler doesn’t force you.
From a purely practical level there is so much already that I had a hard time finding some data type that had no existing and tested solution for it. Looking at the code in the std library can be enlightening.
It's the same as with static vs dynamic typing - some things are just easier to model with dynamic, because you don't have to fight with the type checker to prove that your program really is sound when you know that it is. But, of course, you might believe it to be sound when it really isn't...
These days I'm wanting out of the Terminal - but wherever I go, I'll need to take Kakoune's style of modal navigation with me. I love it too much.
_(For reference, I'm hoping Xi editor implements the foundation of modal editing, and then I can make Kakoune style editing work in Xi Editor)_
Fast forward to ~2 months ago, a work project dictated tight control over memory which, while possible in Go, had me looking at alternatives. I decided to give Rust another try. This time it wasn't just an evaluation, it was needed to work so I bought and Rust book and spent some after hours time learning/etc.
This time, Rust has been an absolute joy. I have no understanding why last time was so painful, and this time it's been so amazing. Maybe it was the book[1]? Maybe it was just a 2nd round of learning based on my previous experience? Regardless, it's been great.
There's just so many mental overheads like what is concurrent safe, what is non-null, etc that are just great to not think about anymore. On top of that, the formatter and LSP are just great. It highlights in my text editor (Kakoune) what variable caused an error, where it gets moved incorrectly, etc. So much just works, it's great.
My only complaint these days is:
1. I find it odd that some things like slice reads can still panic by default. Yes, I can use `foo.get(1)` to avoid panics, but still - it's a bit odd to me. 2. I'm anxiously awaiting async/await. It's quite difficult to be patient.
[1]: Programming Rust: Fast, Safe Systems Development