I'm still figuring my way around rust so obviously some noob questions follow: -> what's with the move/copy mess ? I know why they are needed but it seem to be in the face with all the explicit '&' all over the place in any reasonably sized code. Why not hide it a bit by letting the implicit copy to happen to simpler structures ? (at compile time).
-> Why no love for inheritance? :) - it makes certain patterns easier to implement
-> Why no love for global/static variables ? I know they are prone to be misused but some patterns like singleton really need a lot of shortcuts to implement. And there will always be some cases where you want to keep variables with static and global scope
Rust is much lower level and makes very different tradeoffs. Sometimes for the sake of performance, sometimes to enhance code readability.
But most of the design decisions are there for a reason, and are good choices.
Simple types (that are small and can be trivially memcopied) can implement the `Copy` trait, which makes cloning transparent. For other types, the `Clone` trait is there with `.clone()`. Having expensive copies be explicit is a intentional design decision.
For value conversions, the `Into/From` and `TryInto/TryFrom` traits make conversions a (usually type inferred) function call (.into(), .try_into()), which is really quite convenient, though at the expense of readability.
Regarding strings: they are are definitely complicated and sometimes awkward in Rust. But I'd argue that strings are inherently complicated. Most languages hide this complexity by just allocating and doing everything on the heap, which is not great in a language that values performance and wants to support environments without allocators.
Examples:
Convert.ToInt32(String) – This is _parsing_. In Rust, use `parse`.
Convert.ToString(Int32) – This is _stringifying_. In Rust, use `to_string`.
Convert.ToInt64(Int32) – This is an _infallible conversion_. In Rust, use `into`.
Convert.ToInt32(Int64) – This is a _fallible conversion_. In Rust, use `try_into`.
In all these cases, Rust gives you more immediate semantic information about the conversion, and in fewer characters too!
This is already the case. Built-in types that are simple enough to be copied implicitly already are (roughly: those which don't manage any memory or other resources), and you can enable this for your own types with `#[derive(Copy)]`, as long as they are composed only of implicitly copyable types.
#[derive(Copy)]
struct S {
x: i32,
y: usize,
z: Option<Result<(), ()>>,
}
fn f(x: S) {
// ...
}
fn main() {
let s = S { x: 0, y: 0, z: Some(Ok(())) };
f(s);
f(s);
}
Something like `String` isn't implicitly copyable in Rust, because it manages memory, and therefore copying it would require a heap allocation.The Rust way of forcing non-trivial clones to be explicit is much better than C++ IMO, where someone forgetting a `&` or an `std::move` somewhere can cause an innocuous-looking function call to be arbitrarily slow.
In C# there are not implicit copies either (except of value types), because more complex types in C# are accessed via pointers to garbage-collected heap objects. Rust doesn't have a garbage collector, though.
Rust doesn't deal with "prone to misuse" but "provably safe, else explicitly unsafe, or made safe by the programmer through wrapping code." Global variables are inherently unsafe and need to be wrapped as such.
Of course you'll need global state at some point in your life. What Rust does is yell to remind you that it isn't thread safe.
If you stop thinking about a Rust program as a script where the runtime figures the hard stuff out for you, then these design decisions make a lot more sense. You have a lot more knobs to twiddle than in C#, no garbage collector, and no runtime. The compiler is pretty smart, but it tells you the things it knows and you're responsible for working around the limitations to create sound programs that the compiler can optimize.
> Why not hide it a bit by letting the implicit copy to happen to simpler structures ?
This is the Copy trait.
> Why no love for inheritance
There are a variety of reasons, but one interesting one is that inheritance and strong type inference have issues, and we have very strong type inference. Beyond that, there are various other reasons, but what it really comes down to is that there's just not a ton of pressure to actually implement it; it's not enough of an impediment for Rust users to justify adding it. Most requests come from people who do not write Rust, and once people get into Rust and how it works, they don't seem to need it much anymore.
This is of course very general and there are some people who love it and want it badly anyway, but "some people exist who want this feature" is not enough to make it happen. Rust already has a lot of features, and some people say too many. We have to be careful here.
> Why no love for global/static variables ?
What does "love" mean? Rust absolutely supports these.
First of all, I find that many people aren't using multiple threads, and therefore, what they want is a thread-local, not a static. For that, you can use https://doc.rust-lang.org/stable/std/macro.thread_local.html
If you do need a static, then there are libraries like https://docs.rs/lazy_static/1.4.0/lazy_static/ and https://crates.io/crates/once_cell to help too. We have talked about adding something like this to the standard library, but it hasn't landed yet. https://github.com/rust-lang/rust/issues/74465 is the tracking issue for when it does. The reason that we don't have this yet is that it is not super urgent, given that the libraries already exist.
Now, both of those give you immutable statics, but that's where interior mutability comes in. As you can see from the thread_local examples, you can use a RefCell there, and if your type is simple enough, maybe even regular Cell. For lazy_static or once_cell, you may want to use Mutex<T>, or RwLock<T>, or some other type. For simple integers, the various Atomic* types might be better, for example. The reason this isn't built in is exactly because there are so many options; people need all of these specifics, so we have to provide them, so there's no single built-in thing.
#[macro_use]
extern crate lazy_static;
use std::sync::Mutex;
lazy_static! {
static ref ARRAY: Mutex<Vec<u8>> = Mutex::new(vec![]);
}
fn do_a_call() {
ARRAY.lock().unwrap().push(1);
}
fn main() {
do_a_call();
do_a_call();
do_a_call();
println!("called {}", ARRAY.lock().unwrap().len());
}
I agree that this isn't the most ergonomic, but like most unergonomic things in Rust, there are reasons for it being so.So there's a lot to unpack here, but implicit copies do happen - if it's a copy-able data structure. Strings are huge, so no implicit copy. As far as `&` being too much to identify references vs not, i'm not sure how it could be made any shorter to identify a reference. You're already, commonly, hiding the lifetime associated with that, so it's really `&'a str`, but Rust lets you drop the `'a` most of the time.
Considerable effort has been put into easing the language and lowering syntax. What's left is essentials imo. I _want_ to see when something is a reference. I _want_ to know generic types. etcetc.
> -> Why no love for inheritance? :) - it makes certain patterns easier to implement
I can't speak much here. I've been using Go and Rust for so long i've forgotten what classical inheritance is actually useful for haha. The Go/Rust pattern of Structs, shared behavior, etc cover all use cases for me. i don't find myself missing something, fwiw, but i can't speak to which is "best".
> -> Why no love for global/static variables ? I know they are prone to be misused but some patterns like singleton really need a lot of shortcuts to implement. And there will always be some cases where you want to keep variables with static and global scope
You can have global variables fwiw. Granted, i use `lazy_static` which simplifies it a bit, but there's nothing i'm aware of which prevents this pattern. I've typically just used it in tests though. Globals are the devils candy ;)
let foo1: String = "bar".into();
let foo2 = core::convert::Into::into::<String>("bar");