Rust gnubie. Some places don't allow a question that begins with "Why....". Hopefully this isn't one of those or, if so, someone can direct me to the proper forum. Thx in advance.
Why is there a difference between variable type declaration syntax (including function parameter type declaration syntax), function return type declaration syntax and type cast syntax?
Kindly ignore the silliness of the code -- I'm just trying to show examples of type declaration syntax.
fn main() {
let str1: String = String::from("[str1]"); // ": String" makes sense.
let str2: &String = &str1 ; // ": &String" makes sense
println!("str2={} ; myfn(str2)={}",str2,myfn(&str2.to_string()) );
}
fn myfn<'a>(s: &str) -> &str { // "s: &str". Same syntax. Cool.
// "-> &str" Huh whuh? Why not ":&str" ???
let i: i32 = s.parse::<i32>().unwrap() ;
let i2: i64 = i as i64; // why not just say "i:i64"
return s;
}
:
in expressions used to exist on nightly, doing something different from as
. You'll probably want to read this: 3307-de-rfc-type-ascription - The Rust RFC Book
Note that let x = y as u32;
and let x: u32 = y;
do very different things, so it's a good thing that they're different syntax. (And conventions are moving away from as
to named methods, which is why methods like cast
exist on pointers.)
->
has been traditional for separating arguments and return types for a long, long time. Literally at least half a century, apparently: ML (programming language) - Wikipedia
("Why" is certainly allowed, so long as it's not code for "I think this is terrible and will insist that people agree it needs to be changed"
)
7 Likes
Thx. Good answer. To my lizard brain, the "->" looks like an assignment operator. APL had a left-arrow assignment operator.
If I read : Ty
as "is a Ty
", this would imply to me that myfn
is a &str
.
fn myfn(s: &str): &str /* = */ {
// ...
}
If I read it as "result type from calling myfn
" I guess it works, but that's unintuitive to me; this is a declartion. Rust also has function pointers (and the Fn
traits), so you may see
fn foo(f: fn(&str) -> i32) { /* ... */ }
fn bar(f: impl Fn(&str) -> i32) { /* ... */ }
or coercion from non-capturing closures
let f: fn(&str) -> i32 = |_| { 0 };
or trait bounds
fn g<T: Fn(&str) -> i32>(callback: T) { /* ... */ }
which could be less clear if it was all colons.
Rust doesn't have automatic integer-width coercion, so I wouldn't expect this to work even if we got expression ascription.
let i: i32 = 0;
let i: i64 = i: i64;
I.e. I'd expect the same as this. (Granted I'm already used to Rust.)
Rust uses both ->
and =>
(the latter in match
statements). That one did throw me and I still occasionally typo one or the other. (I believe the "why" is "parsing ambiguity" but couldn't find a citation.)
2 Likes
I did just find this prior discussion about ->
for return types though, wherein a historical FAQ was cited.
2.2 ->
for function return type
This is to make the language easier to parse for humans, especially in the face of higher-order functions. fn foo<T>(f: fn(i32): i32, fn(T): U): U
is not particularly easy to read.
6 Likes
Personally, the way i've always read :
is as "of type" and ->
as "to".
so for fn myfn(s: &str) -> &str
reads exactly "myfn(s of type &str) to &str", definitely seems to work less well with trait and lifetime bounds though.
5 Likes
I would just hate to see Rust devolve into a punctuation gumbo with every-semi-useful-feature-under-the-sun.