Are "why?" questions allowed? Like for differences in type declaration syntax

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" :wink: )

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"[1] 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.)


  1. "type of the expression myfn(some_str_ref)" ↩ī¸Ž

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.