This is the right way to learn. I'm quite familiar with lifetimes and whatnot, but when I didn't bother with them much at all when learning - I just "clone"'d.
This allowed me to learn 95% of the language, and then once I did that, learn the last 5% (lifetimes).
Highly recommend.
Non-lexical lifetimes are, in my experience, pretty uncommon in most non-library code. You don't really need them until you really need them.
To avoid confusing the newcomers: lifetimes are always non-lexical (see [1] for the pedantic details.) I suppose you meant that explicit lifetime annotations are pretty uncommon, which is not wrong.
[1] https://blog.rust-lang.org/2022/08/05/nll-by-default.html
An atomic memory operation! These can be orders of magnitude slower than regular memory operations.
/Overly pedantic
No, this also applies to (non-relaxed) atomic loads and stores, depending on the platform.
> Atomic non-seq-cst load/stores can be cheap.
Relaxed atomic loads and stores are always cheap, but anything above requires additional memory order instructions on many platforms, most notably on ARM.
Here we are talking specifically about mutexes, which follow acquire release semantics.
To be clear: locking an uncontented mutex is indeed much, much cheaper than an actual call into the kernel, but it is not free either.
But even on ARM, these days store releases and load acquires, while not as free as on x86 are very cheap.
To make my statement more precise, typically what is still expensive pretty much everywhere is anything with #StoreLoad barrier semantics, which is what you need to acquire a mutex.
I'm not saying that's a good thing.
If you're creating a new string, then sure return String. But if you have a path where you could just return the original input, consider returning a Cow<str>.
Insofar as taking an impl into, burntsushi linked to a rust playground demonstrating where that approach falls down. In general (heh) taking arguments , especially options or stringy ones, that are generalized over an into impl is one of those things that seems real nice at first but gets real unpleasant pretty quick IMO.
I don't think String could be a Cow<str>. Remember, Cow<str> is actually a Cow<'a, str>, and if you want to borrow a &str from a Cow, the lifetime of that &str is not 'a, but rather, attached to the Cow itself. (This is necessary because the Cow<str> may contain an owned String.) This in turn would effectively kill string slicing.
In order for something like Cow<str> to be the default, you need more infrastructure. Maybe something like hipstr[1]. It is a nice abstraction, but not one that would be appropriate for std.
And it's even okay beyond just starting with.
Search the regex crate repository for clones. There are a lot of them. Hell, Regex::new accepts a &str for a pattern, and one of the first things it does is convert it to a String.
`Cow` would be a needless and gratuitously bad type to accept for Regex::new. There's no point. It would very likely suffer the same class problems as using Into<String>, because we'd need to use Into<Cow<'_, str>>, and thus it is susceptible to annoying inference failures.
It's always important to contextualize costs. In this case, regardless of the size of the pattern string, cloning it is always marginal relative to the other costs incurred. (One possible exception to this is if the pattern is just a simple literal with no regex meta characters. There in theory could be a fast path to side-step regex parsing and other things, but in practice there's not much need for that.)
2.) Be careful because &String is not &str, although in many cases you can pretend thanks to magic of AsRef/AsDeref.
4.) If you find yourself calling is_none, rethink things a bit. Pattern matching and the various destructuring (e.g. if let) features are some of the most powerful tools at your disposal with Rust. This is where experience with something else first (e.g. Elixir) can be helpful.
I rarely introduce a lifetime to a struct/impl.
IMO the big use case here is &str. Arc kind of bails you out of a lot
While that's totally reasonable it's good to remember that you're essentially trading compile time guarantees for runtime ones. If you can figure out how to beat the borrow checker into submission that's one less thing to debug at runtime. primitive like u32 and it's borrowed and you had to dereference it or clone it.
The primitive types should all implement Copy which means you should (almost?) never have to explicitly clone them. Dereferencing is another story tho.Nah, go with an ML-family language. Maybe even Standard ML, because it will nudge you away from writing "C in ML" and encourage you to pick up the idiomatic way of doing things. (Laurence Paulson's book has an online version available for free on his homepage).
Rc will be faster (if that matters to you) than Arc but it can't cross threads. (Safe) Rust will check you didn't get this wrong, so there's no danger but obviously knowing ahead of time avoids writing an Rc that you then need to be an Arc instead.
Sometimes it's tidier to write the borrow in the type of a pattern match e.g. if let Some(&foo) = ... Means you won't need to dereference foo inside the block.
Other things I would add to this list:
- For structs that don't own anything on the heap, derive Copy. Then you can just pass them around willy-nilly without explicit clone()s
- Using a functional style where it makes sense to helps a lot; it can be really easy to pass things by reference when they only need to be temporarily, immutably used by that one function. And if you make Copyable structs, you can pass owned structs and return owned structs and not worry about any of it
1. .clone() / .to_owned()
1. String -> vs &String/&str with & borrow
1. lazy_static / OnceCell + Lazy / OnceLock
1. Arc<Mutex<T>> with lots of .lock(). What would start off as like NULL in Java/C is Arc<Mutex<Option<T>>> and you have to check if it is .is_none()
I think that's about it. I rarely introduce a lifetime to a struct/impl. I try to avoid it honestly (probably for worse). Arc kind of bails you out of a lot (whether that's for good or not I don't know).
edit: Remembered another. I think it's kind of weird/too verbose from the compiler / borrow checker when you have a primitive like u32 and it's borrowed and you had to dereference it or clone it.