Remember that &str is not a type — it's a family of types. The lifetime component hasn't been specified. The restrictions they're talking about are that it has to be possible to write a suitable lifetime.
… since team names are hardcoded literal strings and won't be changing.
In that case, &'static str is an appropriate choice of lifetime, and you will not experience any particular restrictions. You would if you tried to make use of a map containing run-time constructed strings, which would have to use some lifetime shorter than 'static.
However, &'static str is not the most efficient option for a compile-time set of choices, because it still requires reading the entire string to confirm that items are equal or unequal. The best choice here is an enum.
(You can also use strum to generate stringification code like that for you.)
Using an enum also enables you to use more efficient structures than HashMap for some applications, because if you know there are only two (or however many) choices then [i32; 2] is much more compact and faster to access than HashMap<Team, i32>. But that's a further performance optimization, not something you should do automatically when you're just getting started.
&str isn't just some faster string, it's for restricting the scope where the values can be used. If you want a smaller non-growable string, use Box<str>. Box<str> and &str are essentially the same type, only with different ownership. They have identical representation in memory (pointer, length), but &str can be used only in the scope of the variable it borrows from, and Box<str> can be used anywhere.
In C++ terms, &str is a std::string_view, so it's not a string itself, it's a view into String/Box<str> or similar stringy type that has to already exist somewhere, before &str view of it can be created. And &str can be used only temporarily, only in the scope where that other String it borrows from exists, and cannot outlive it, and the data it borrows from cannot be mutated.
It's also impossible to create a new &str and return it from a function. You can create and return String, Box<str>, or Cow<str>, but returning a new &str is a use-after-free bug (that the borrow checker will prevent). In C++:
std::string data = "hello"; // let data = String::from("hello");
std::string_view reference = data; // let reference: &str = &data;
return reference; // reference does not live long enough error
I'm also new, coming from a java background. I've struggled a lot trying to solve the chapter 8 exercises only by reading chapter 8. Specially the one regarding the association between a person and department, that I'm still working on.
Without thinking about this Box thing (that is only explained at chapter 15, Smart Pointers - The Rust Programming Language, smart pointers, and I haven't read it), what I ended up doing was reading chapters 8 , 9 and 10 and 11 to understand generics and lifetimes. And to be able to perform test-driven development.
It was already confirmed but it should be stressed again: Rust is extra hard to learn for people with some experience, surprisingly enough.
People with lots of experience and knowledge of differently typed languages (like Haskell, OCaml or Scheme) usually pick it up easily. As do the people without any experience.
But people with limited experience often face the need not to learn something but to unlearn something… and that often leads to this incredible frustration and peculiar learning curve. In particular:
You would find that Rust quite unfriendly to these attempts and, most of the time, they are not needed.
The problem with test-driven-development in Rust lies in precisely what many consider it's most awesome advantage: incorrect code tend to just fail to compile! Of course that blows up the plan to use test-driven-development to smithereens: if you simply couldn't write code that doesn't work then how to do write test that is failing to then start fixing it?
Instead of test-driven development in Rust you usually have compiler-driven development: you design you data structures, write code and try to convince complier to accept it. Once that's done… that's it. Time to add some high-level integration tests and ship it.
That doesn't mean that unit-tests are entirely impossible or that mocks are never used, but these are “tools of last resort”, when everything else fails. And they either come from very unusual requirements (e.g. when you want to develop software for self-driving car and each test run would otherwise need actual trip with human supervisor… mocks are definitely preferable) or, more often, when your design is too strange for normal compiler checks to catch errors in it.
Yep. One of the things that bothers me worst is that , at least where I've got in the rust book is that references with the borrowing checker tend most often than not simply "do not work" . For example in the chapter 8_3 exercise, I've just tried to make a hashmap with &str as a value, which fails miserably due to the lifetimes problem.
The new version of my database.rs has &str replaced with String and "BAM!" all those pesky lifecycle specs vanish and everything "fits".
But it annoys me to big extent that in the end I have just to copy the objects everywhere ...
Instead of simply send references of them.
Are you sure this in the end is not a big memory waster and a performance chugger?
But I don't have complains with unit-testing. I like them. Even when they don't compile they provide a clue to why.
I'm really wondering what kind of code you write. For me, the errors I try to fix with tests are locigal errors. I don't test if the return type of a function is correct. Of course it is, I'm using a statically typed language. I test if the value that comes out makes sense.
Because I often write numerical algorithms for which it is easy to write test cases but hard to spot errors in formulas. Also I often will want to optimize these algorithms later without changing their behavior. Tests are really usefull here. Especially for corner and edge cases.
Thanks for explaining your use cases. Your lack of need for unit tests now makes much more sense.
In most cases there is not a lot of copying, because once the String has the right owner, it can be borrowed as &str from that owner.
In a situation where excessive copying seems inevitable, you can use reference counted Arc<str> instead of String. Do consider running a benchmark to see what version is actually faster in your use case, though.
Yes, it works. While you are using static, defined at compile time, &str. So this works for a hashmap for storing maps between constants, for example. But at the moment you try to have these constants in a configuration file loaded on application startup this starts to give problems.
You probably were taught by well-meaning teachers, but it's time to accept the truth: world have changed.
Decades ago, when languages like Lisp rules and Java was dreamed up every byte counted and references were cheap. Today… it's the opposite. If you look on 10 years old the table you'll see that you need approximately the same time to copy 1K of data as you need to traverse one, single, reference to it's source if reference happens to be not in cache but in main RAM!
Exactly. Not an &str. A String. So, as a gross rule of thumb, a Hashmap of &str, in keys or values, even static ones, is not in general a good choice, comparing to a String, except on very very specific cases.
In my specific case in the database.rs code it is not even on startup, it is really an arbitrary lifetime.
So, I may be sounding silly, because I'm a total noob in the language, but this is my "initial approach" to the model. Oooga booga! String works! str no good ! Oooga booga!