Some answers:
You can't overload as
. But you can implement the From
trait, for conversions that consume the value, or the AsRef
or Borrow
traits.
You can't get a data race per se in single-threaded software. Here's Java's definition of a data race:
A data race occurs when:
- two or more threads in a single process access the same memory location concurrently, and
- at least one of the accesses is for writing, and
- the threads are not using any exclusive locks to control their accesses to that memory.
So being multi-threaded is a prerequisite.
What you can get in single-threaded code is reentrancy errors. Rust prevents these. For example:
fn append(vec: &mut Vec<i32>, elts: &[i32]) {
vec.extend(elts);
}
fn main() {
let mut v = vec![0,1,0,-1];
append(&mut v, &v);
println!("{:?}", v);
}
This doesn't compile in Rust (playground), but if it did, it could be a very serious bug if v
were reallocated to make space for more elements while append
's argument elts
was still pointing at the old elements. This kind of mistake, where code assumes two pointers are distinct and chokes when they're actually referring to the same thing, is quite common. I think you can look at it as a very simple case of unexpected reentrancy - where a data structure is being consulted while it's in the midst of being modified. In Java, this kind of problem often shows up as a ConcurrentModificationException.
When Rust lays out a struct in memory, it places its fields' values directly in the struct, rather than just storing pointers to them, the way JavaScript, Python, or Java would. Rust would like to be able to assign each field a fixed offset from the start of the struct. That is, if I say:
struct Point { x: f32, y: f32 }
Rust wants to be able to say that, if a Point
lies at address A
, then its x
field always lies at address A + 0
, and its y
field always lies at A + 4
. (Or something like that; Rust doesn't promise exactly how it will lay out structs.) This means that simply knowing a value's type and address is all you need to find all its fields, and there's no need to store hidden fields in the value.
So to your question: If you had dynamically sized values in the midst of a struct, then any subsequent members' offsets would vary dynamically, which Rust wants to avoid.
Rust's .
operator automatically follows references in its left operand, so you can write r.x
instead of (*r).x
. It will also borrow a reference to its left operand if necessary, so you can write v.len()
, not (&v).len()
.
Comparison operators also implicitly borrow references to their operands, so you can write string1 == string2
, and not lose ownership of either. Or, if you have references, comparison operators automatically follow them. So if you have two &i32
values r1
and r2
, you can write r1 < r2
, instead of *r1 < *r2
.
If you pass println!
a reference, it implicitly dereferences it and just prints the value it refers to. It also implicitly borrows references to its arguments, so (say) passing a String
to println!
doesn't steal ownership of it from the caller. So println!
is designed to be especially flexible, more so than an ordinary function.
There are probably a few other cases that automatically dereference, for convenience. But otherwise, you generally do need to write out uses of the &
and *
operators. I usually just let the compiler tell me when I've gotten it wrong. You'll get used to it eventually, and your mistakes will become rarer.
(Details: println!
uses the formatting traits from the std::fmt
module, like Display
and Debug
, to produce the textual form of a value. If a type T
implements a formatting trait, then there is a generic implementation of that trait for &T
and &mut T
as well that simply follow the reference and format the value it points to. The comparison operators are driven by the PartialEq
and PartialOrd
traits, which have similar generic implementations.)
I'm pretty sure I don't understand this question; can you expand?
Programming is like playing violin or piano: it takes thousands of hours of practice before you can say you're good. The people who get good are those who put in the hours; whether they do so because they love it or because they are disciplined doesn't matter. So treat Rust like learning a new instrument. Read Rust code written by great programmers (Raph Levien and Tomaka are two of my favorites, but there are lots of others). Contribute to a project you like. Write lots of code yourself.