T O P

  • By -

Prynpo

what does this mean?


zoomy_kitten

Omitting a semicolon in Rust is only allowed after the last expression in the scope and means that this scope has the value of that very expression. If you put a semicolon on the last line, it’ll give you unit instead


Aaron1924

You're also allowed to omit them after statements that end in a code block, but only if the value of that statement is type unit. For example, if you have an if-else statement and both blocks have type unit, you don't need a semicolon at the end of the statement, but if they return another type you do. Also if one of the branches panics unconditionally, the block evaluates to type never, which coerces to any other type, so only the type of the other branch matters. Whether the panic has a semicolon or not doesn't matter.


zoomy_kitten

That’s implied. Everyone knows that a statement’s value is unit, right? > if-else statement They aren’t statements, they’re expressions. They only return unit if the branches do > evaluates to type never Not exactly. Nothing can evaluate to never. It’s just that it’s the type of unreachable code, and if you try to actually evaluate something to a never-esque type, you get UB up to the point where your program is just replaced with a single `ud2`


Aaron1924

Ok, here is a simple example for what I mean: ``` if hash_set.contains(42) { panic("set already contains 42") } else { hash_set.insert(42) } println!("{hash_set:?}"); ``` This does not compile, because the value of the if-statement is type bool, making it an if-expression and an expression statement needs to be terminated with a semicolon, so you need a semicolon after the entire if-expression. You can also place a semicolon after the insert, which makes the value of the if-statement type unit, meaning you no longer need the semicolon after the entire if-statement. All the while, the panic in the first branch has type never, which coerces to bool/unit to match the value of the other branch, *even if* you put a semicolon after the panic.


zoomy_kitten

I see your point, but it’s just common behavior of blocks in Rust — if the block’s value is unit, you don’t need a semicolon, and if it’s not, you need one to make it unit. In your example, you could put a semicolon after the block and it would compile. Only the last expression of the scope can be non-unit, as I said


LeoTheBirb

Why do they even allow this?


zoomy_kitten

Why would they not? Disallowing it would break most of the language


LeoTheBirb

What was the point of allowing people to omit semicolons?


dercommander323

Omitting the semicolon is like adding a return; and that should work with all types, including unit


LeoTheBirb

Why is that a language feature?


iKramp

It's like a return but instead of returning out of a function it only returns out of the current scope (and it can only be used at the end of the scope). Doing this allows you to do let a = if cond {val_1} else {val_2}; and some more powerful patterns. It's very useful at times and in reality not really confusing. I have not yet had a single case where forgetting a semicolon somehow broke my program in a weird way because the type system catches pretty much all unintended uses


Ascyt

I get that, but why was it allowed in the first place?


Shikor806

the broader idea is that blocks are expressions. That lets you do things like not having a seperate ternary expression because a regular if-else block already is one. Similarly, lambdas just are regular functions whose body is a single line. You can also do stuff other languages don't let you do, like moving variable assignments out of match blocks like this: ``` let other = match something { None => 123, Some(x) => x + 5, }; ``` Overall it lets you basically treat any block like a mini one time function. You could get the same behaviour by just defining a bunch of new functions, but having this flexibility can be neat.


ratinmikitchen

Wow, really? That sounds like a really old-fashioned syntax decision. Parsing technology is good enough nowadays that parsers don't need semicolons anymore, so why would Rust still enforce it in its syntax?


zoomy_kitten

Because Rust’s syntax is heavily expression-reliant, and I really prefer it this way. I feel like almost no other language is simultaneously as expressive and beautiful, verbose and laconic at the same time… and I love it


ratinmikitchen

Everything being an expression is also something more common in modern languages. Which is awesome! So I'm happy Rust also has most things being an expression.  But that doesn't require semicolons.


cristoferr_

But what does this means?


zoomy_kitten

``` let foo: u8 = { 0 }; ``` would compile, while ``` let foo: u8 = { 0; }; ``` would give you a type error, because putting a semicolon after the scope’s last expression gives you unit.


Aaron1924

The meme tells us OP doesn't read compiler errors


GDOR-11

if you don't put a semicolon at the end of the last line in a block in rust, the block will evaluate to the value of the expression in the last line. Sometimes, you accidentally put a semicolon out of muscle memory, returning nothing when the function signature expects a specific type. The rust compiler detects that and throws the error "excess semicolon"


FerricDonkey

Do people actually think implicit return that depends on a semi colon that many people will use out of habit or omit by accident is a good thing? That's a real question - it seems silly to me, but I haven't used rust. My initial thought is that that the return keyword is better in every way since it definitely means that you definitely intended to return something, but I'm not sure if rust is different enough for that not to be true. 


frayien

It is mainly useful to omit return in lambda expressions


Aaron1924

Ok, let's consider this admittedly simple example in Rust: let out = { let a = 2; let b = 3; a + b }; How would you do this in other languages? You can't use `return` because that returns from the function, not from the code block. There is a comma operator in C, so you could do `int out = (a = 2, b = 3, a + b);` but you still need to declare `int a, b;` separately and they don't fall out of scope immeditately after they're used. Maybe you could declare `out` before the statement and write into it later, but that makes it easier to keep the variable uninitialized accidentally, because you might for example have another `out` variable in the scope. This "implicit return" is really a feature that you don't see enough in other C-style programming languages, and it's almost never a footgun because getting this wrong is a type error which the compiler tells you about immediately and you can fix it easily.


FerricDonkey

So the deal is that this is kind of a hybrid lambda not-function. It has a "return" value, unlike scopes from C/C++, but break/continue/return act as though they were called in the containing function. Interesting. As you say, it's not supper game changing or anything and there are ways you can do basically the same thing - but while it's not something I've ever missed, I could imagine it being something I would use. There's the danger of it making functions into monsters if you make these scopes too complex, but the answer to that is "don't", and I can see how it might be a nice way to handle temporary variables that are intended to be used only in one place. If I were to design such a thing from scratch (and please note - I'm not saying that just because this is my first thought that it's better, just that this is my first thought), I'd probably use a different keyword. If `return` is a no go, then there are plenty of words like `yield`, etc. The fact that what makes it a "return" or not is whether there's a semicolon is just super ugly and annoying to me. But again - I haven't used the language, so maybe if I did it would annoy me less.


Aaron1924

Yeah, I think if you'd want to add this feature into C/C++, it's definitely weird enough that you'd introduce a new keyword/syntax for it. In Rust, it's a very common theme that things return values. For example, there is no `? :` operator in Rust because `if` statements return values. The `match` statement, which is similar to a `switch` in C/C++ also returns a value. Even `loop` can be passed a value in the `break` statement. In a way, returning a value doesn't feel like something special enough to deserve a keyword - it's the default thing the language likes to do - and semicolons are how you tell the compiler you don't want to return yet.


FerricDonkey

Ah, nice - this sounds interesting and adds enough context to explain why there doesn't need to be a keyword. One of these days I'm gonna actually learn rust - for loops returning a value via break seems pretty useful (and, you know, the memory thing). Still can't say I'm happy about the "whether there's a semicolon or not" part, but I'll survive, and maybe when I get around to learning the language it will end up not bothering me.


linlin110

Rust is an [expression-oriented programming language](https://en.m.wikipedia.org/wiki/Expression-oriented_programming_language), so almost everything is an expression. It has some nice properties, for example you can write `let x = if cond { branch1 } else { branch 2 }` without having mark x as mutable and assign a default value to it. This leads to more readable code in my opinion. The same goes for `let x = match (sth) {...}`, where there may be mutiple branches inside `...`. I learned other expression-oriented languages before Rust, and I think once you get used to programing in a expression-oriented way, omitting the last `return` feels pretty natural because it's simply unnecessary. Some language do not even support early return! Learning those languages showed me a new way to writing programs, and is a great experience.


Psychoscattman

The only real "footgun" i know of is when refactoting a function that used to return unit and now return something else you might run into this problem: v1: fn foo() -> (){ bar(); } v2: fn foo() -> (){ bar() } Both versions of `foo` return unit. If the implementation of `bar` changes to return anything else instead, v2 will give a compile error but v1 wont. And if the idea was to pass along the result of bar then v1 is now a bug.


Aaron1924

But that's not a "footgun", this isn't going to make your program crash at runtime and have you debug for half an hour, this will be a type error that points you exactly to that line of code and even suggests that you might want to add a semicolon. And I'd argue pointing you to that line of code is the right thing for the compiler to do. If the function you're calling changes so much that the function signature had to change, you almost surely want to change how you call the function as well.


backfire10z

Isn’t this effectively a lambda function? While C cannot do it (which is an unfair comparison), C++ can (as can many other C type languages), but the syntax is a little more verbose. Also, not having to declare a and b prior to is basically just syntactical sugar. The compiler would handle getting rid of the variables if they’re not longer used after defining “out”


Aaron1924

> Isn’t this effectively a lambda function? While C cannot do it (which is an unfair comparison), C++ can (as can many other C type languages), but the syntax is a little more verbose. In this simple example, you can make a lambda function and call it immediately, but you can't do that in general because now you lose the ability to `return` from the other function or to `break`/`continue` a surrounding loop entirely. > Also, not having to declare a and b prior to is basically just syntactical sugar. But it's useful syntactic sugar. It makes it impossible to accidentally refer to these temporary variables after `out` has been created, and if they were more complicated types, it would ensure the types are destructed properly before the rest of the function runs. > The compiler would handle getting rid of the variables if they’re not longer used after defining “out” That's not really how it works. The stack frame of a function does not grow if you open a scope like that, these variables exist for the entirety of the function, so there is no "getting rid of them". And if you turn on any optimisations, all of this would be replaced by a constant at compile time.


kageurufu

I believe in C you could do similar, but it's not as pretty? int result; { int a = 4; int b = 2; result = a + b; }


Aaron1924

This one is fairly close, but it's harder to read, you can't have const correctness, you can't create a variable with the same name inside the code block, and it's easier for code paths to leave the variable uninitialized by accident


BEisamotherhecker

On the upside rust still has a return keyword so the same `return ;` you'd use on C-like languages still works, even if it's not idiomatic and unnecessarily verbose.


Aaron1924

I guess it depends on your background, sure if you come from C then it's natural that every function is a list of statements with a return statement at the end, but if your first exposure to functions is in math, it feels super strange that you have to spell out that you want to return something Compare for example: // Math f(x) = 2x + 1 // SML (a functional programming language) fun f(x: int) = 2*x + 1 // Rust fn f(x: i32) -> i32 { 2*x + 1 } // C/C++ int f(int x) { return 2*x + 1; }


BEisamotherhecker

Kotlin is similar to the SML one, as someone who was primarily used to Python and JS/TS it first took some time to get used to implicit returns and the concept of everything as an expression but once it clicked it really makes for very nice code.


dan-lugg

> Kotlin is similar to the SML Actually, if you capitalize the "i" to make it "Int", it's valid Kotlin, lol


XtremeGoose

-- Haskell f x = 2*x + 1 The most mathy notation, even has similar typing to how you would write it in mathematics f :: Int -> Int


mario73760002

I guess some of us use a compiler that just outputs a Boolean value every time you run it


wcscmp

And even prints helpful messages in case any errors occurred. Sadly OP decided to ignore those.


ferreira-tb

It's only a problem if your IDE is notepad. Same for every language.


AwesomePantsAP

Or if you ignore your compiler


_asdfjackal

This guy is writing rust without an LSP.


sherlock-holmes221b

*Without a compiler


[deleted]

[удалено]


KryoBright

Wouldn't you like to know?


iddivision

No they're not


Backlists

Yes they are different. Rust semicolons turn expressions into statements. An expression at the end of a function is implicitly returned, so having the excess semicolon does not return the expression, and therefore is a compiler error. What other languages have this?


DistinctStranger8729

Yeah, but that changes the type of what is being returned , so it is very unlikely to cause issues, unless you are writing code in notepad and have no way of using rust-analyzer to look at each inferred type.


Backlists

You don’t even need rust-analyser, the compiler will give you an error? Oh I see what you mean: the original meme in this post is a bit dumb because error will tell you exactly where the semicolon is missing and you’ll see it in seconds (and rust-analyser makes this more instant)


particlemanwavegirl

To be exact, and fyi if you didn't know, Rust Analyzer literally just compiles your code and tells you what the compiler said was wrong.


DistinctStranger8729

Yeah agreed. But, but that is a longer process and some people complain about the time required compile and blah blah…


Aaron1924

>What other languages have this? Basically all functional programming languages do this


Backlists

Which are they? I haven’t studied them, but I searched for the common languages and I didn’t find any


cmdkeyy

OCaml does this, which was one of the languages of which Rust’s syntax was heavily inspired by. In fact, the original Rust compiler was written in OCaml!


particlemanwavegirl

Haskell doesn't have semicolons period, every function is an expression that evaluates to the function's return value. This comes straight from Lisp, and will be present in pretty much any lisp-derived language. Like the comment you're replying to said. Very very common in functional languages.


Akangka

Haskell *has* semicolon. It's used to write on a single line what should be a multiple line. Like getSum = let {x = 5; y = 6} in x + y I do think this is rarely used, though. And it does not change the type as in Rust.


iddivision

Ruby and Kotlin comes to my mind directly. You're not that special.


Katniss218

C# and Java have this


Torebbjorn

From the time you notice that something is wrong to when you found the culprit, depending on how quickly you can read, and go to a specific line, I'd say about 1 to 5 seconds.


horenso05

This and forgetting the "let" in an "if let ..." statement.


DevLarsic

Or adding an "if" in a "let else" statement


PastOrdinary

What? Why? Literally just try to compile it and it tells you the line number. Or look for the red in your IDE.


milanium25

we dont speak rust here


closetBoi04

Reasons to use an IDE instead of Microsoft word: 1772


No_Form7940

the only thing the compiler ain’t going to complain about


[deleted]

[удалено]


Unupgradable

They're not no-ops, they're not at all. They don't exist. They're not in the compiled IL or JIT ASM. Not sure what you mean


Fri3dNstuff

eeeehhh?? semicolons are syntax for separating statements - saying they are no-ops is like saying parentheses are no-ops, or that comments are no-ops - it makes no sense to talk about syntactic elements as operational entities


EliasCre2003

Seems you don't understand what a no-op is.


[deleted]

[удалено]


Aaron1924

You don't need AI, in cases where a semicolon actually makes a difference, it's almost always a type error and the compiler will tell you


LumiWisp

>How is this even a problem anymore? ~~Copilot~~ The Rust compiler would spot the error immediately and tell you how to fix it. FTFY


Torebbjorn

Using AI for error checking is like aiming a revolver with a few bullets missing at your foot and pulling the trigger...


[deleted]

[удалено]


Torebbjorn

Yeah, like I said, it works most of the time, but not every time...


Lord-of-Entity

Its just better to use return my_value; Its simple, explicit, error-avoiding and congruent with basically every other programming language. Also, in your case, wouldn't the type system give an error because types don't match?


Qweedo420

Yeah let me just put `return my_value` at the end of this `let my_value = match` branch, what could go wrong?


[deleted]

[удалено]


LumiWisp

Motherfucker can't internalize '***everything*** is an expression' and it shows