How to correctly return slice

I found that there are two kinds of people: Those who want strict guidelines and one definitive answer, and those who prefer many ways. Rust might be a bit easier for the latter group.

In my experience, Rust has made a few design choices that make the language less familiar to new users, but once you learn them, are an improvement compared to what those other languages offer.

In my opinion, the thing discussed here falls into the category above.

8 Likes

Every(most) language allow that. No language AFAIC has different ways of returning from function.
And actually, what problem are you solving by having multiple ways of returning from function?

How so? What advantage does it give? At best it confuses newcomers...
@s3bk

But rust is very strict here. It does not allow user not to use return. So it contradicts what you've said about giving users freedom of "solving problems" in many different ways.

Everything in Rust is an expression if it can be, and computing values using this, e.g. with a match on some enum and describing the resulting value in each branch becomes very natural.

It would be very inconsistent if functions were an exception to this rule.

As for multiple ways to return, there is only one idiomatic way to return a value, and that is to leave out the return keyword unless it is an early return.

2 Likes

Ruby, at least, does it the same as Rust - returns the last expression, or with the explicit return statement.

I feel you confusion. It's likely confusing because of ones experience with some other languages.

But on reflection I think it is sweet and makes a lot of sense. Because:

  1. Having to write "return" at the end of every little function is verbose and redundant. Contrast:
fn square(x: u32) -> u32 {
    x * x
} 

with:

fn square(x: u32) -> u32 {
    return x * x;
} 
  1. Expressions in Rust return values, for example "match":
let binary = match boolean {
        false => 0,
        true => 1,
    };

Here the "match" expression has to return values of 0 and 1. Having to write "return" there would be horrible:

let binary = match boolean {
        false => return 0,
        true => return 1,
    };

And wouldn't that be ambiguous? Do I want to just return from the match expression there or return from the function I'm in?

So for consistency with all other Rust expressions it follows that functions should not require a "return" at the end.

  1. Sometimes you want to bail out of a function 10 levels deep in loops and conditional blocks. You need "return". In this case the act of returning from the bowels of your function is a big deal and should be pointed out with a nice big, syntax highlighted, keyword.

  2. In Rust then, those semicolons have meaning. In most languages they pretty much do not. See Javascript. Even in C I often think they are pointless.

  3. Whilst this may be a new and unfamiliar use of the semicolon that one trips up on occasionally that is not a worry, the compiler let's you know soon enough and even suggests fixes.

2 Likes

They are doing two different kinds of things. The implicit return at the end of the function doesn't alter control flow, while the explicit return that is needed for the non-last expression does alter the control flow.

Your complaint is akin to saying that it would be "very confusing" that one doesn't need to write a for loop if one wants to execute a statement only once, and it would be "clearer" to write for i in 0..1 { do_stuff(); } in every case.

Yep.

I think the confusions stems from the fact that in many languages "return" does two jobs.

Firstly "return" is about control flow. If you want to bail on a function in the middle of some tangle of loops and conditionals you need "return".

Clearly for this job "return" at the end of a function is redundant. There is nowhere for control flow to go other than out the end of the function and back where it came from. Even C does not require a "return" at the end of a function.

Secondly "return" selects the value that the function should return to the caller. Clearly that is mostly redundant as well. See my example above.

Rust separates these to jobs of flow control and determining a functions value rather than overloading "return" with them both.

Even down in the bowels of the machine instruction our programs compile to we see things like:

someFunc:
...
MOV AX, 2             ; Set the functions return value into register AX
RET                   ; Return to the caller.

(Forgive the syntax there). We see that setting the functions values and actually returning are separate jobs with separate instructions.

People are confused by experience of languages that get it wrong. Rust does it right :slight_smile:

3 Likes

That is true except that C does require an explicit return at the end of the function (the only exception is returning void).

2 Likes

I actually disagree:

&s[i..]
is equivalent to:
return &s[i..];
If that's true then I should be able to use any of the form.

That's where the confusion arises. In general, expr is very different from return expr;.

expr is an expression. Expressions can be used at the end of a block, in which case it serves as the value of the block; when the block is the function body, this is equivalent to returning from the function. Note that the lack of semicolon, in general, is not associated with returning; otherwise, let bytes = s.as_bytes(); would interpreted as let bytes = { return s.as_bytes() }; (let's not get recursive), which isn't helpful.

return expr, on the other hand, is a diverging expression that, when evaluated, returns expr from the enclosing function.

There are two ways of returning from a function:

  • letting control flow reach the end of the function; and

  • explicitly returning via return.

It's similar to how a while loop can end in two ways: by reaching the end, or by early exiting via break.

If you find this behavior confusing, you can choose to always use return, as you would do in many languages; however, utilizing the expression-oriented nature of Rust is considered more idiomatic, and many learners will gradually get used to it as they get more familiar with the language. So don't think into it too much if you feel puzzled at this point — you will gradually get used to even "stranger" things as you learn more.

1 Like

It actually makes sense what you've just said.
Thanks

3 Likes

This is actually a common pattern in expression-based languages. For example, Rust's behaviour is identical to how returns work in Ruby.

You also see it in most functional languages and lisps. I'm guessing you're coming from statement-based languages like C, where expressions aren't nearly as useful.

Julia does the same thing:

The value returned by a function is the value of the last expression evaluated, which, by default, is the last expression in the body of the function definition.

Haskell too.

Mathematica as well.

The list goes on...

Opinion: explicit beats inherent. That there are syntax dependencies related to returned values indicates to me that perhaps it would be better to have explicit indication of return. Imagine how easy it would be for a keyboard grunt to find ALL the places were a value is returned vs having to run the code through eyeballs -> {{Rust Syntax Knowledge Dedicated Brain Cells}}.

Rust gets right what many of these other languages get wrong. Returning the last expression is dangerous if semicolons on statements are optional. In that case, it's all too easy to add a line to the end of a function and get a mysterious error. (Been there; done that more than once.) That can't happen with Rust because the previous last line won't have the required semicolon, and the compiler error alerts you to your mistake.

You're more than welcome to use the clippy lint that warns if you have an implicit return. Most would consider it unidiomatic Rust, but that's your decision to make.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.