Explicit function exit markers


#1

Rust has the paradigm of avoiding implicit functionality, which is pretty good. So the code is readable and good to parse by human eyes.
But I always have problems finding all positions where a function returns when there’s no explicit return statement but a missing semicolon.
If I have a complex function with nested if and match blocks, it’s hard to find these in the code.

I would like, if I could mark these with some (optional) symbol, e.g. ->

A real world example:

        match self.stream.write_all(&buff[..]) {
            Err(e) => Err(Error::Io(e)),
            Ok(_s) => {
                let mut reply = vec![0; MODBUS_HEADER_SIZE + expected_bytes + 2];
                match self.stream.read(&mut reply) {
                    Err(e) => Err(Error::Io(e)),                    
                    Ok(_s) => {
                        let resp_hd = try!(decode(&reply[..MODBUS_HEADER_SIZE]));
                        try!(Transport::validate_response_header(&header, &resp_hd));
                        try!(Transport::validate_response_code(&buff, &reply[..]));
                        Transport::get_reply_data(&reply, expected_bytes)
                    }
                }
            }
        },
        Err(Error::InvalidData)

vs.

        match self.stream.write_all(&buff[..]) {
            Err(e) => Err(Error::Io(e)) ->,
            Ok(_s) => {
                let mut reply = vec![0; MODBUS_HEADER_SIZE + expected_bytes + 2];
                match self.stream.read(&mut reply) {
                    Err(e) => Err(Error::Io(e)) ->,                    
                    Ok(_s) => {
                        let resp_hd = try!(decode(&reply[..MODBUS_HEADER_SIZE]));
                        try!(Transport::validate_response_header(&header, &resp_hd));
                        try!(Transport::validate_response_code(&buff, &reply[..]));
                        Transport::get_reply_data(&reply, expected_bytes) ->
                    }
                }
            }
        },
        Err(Error::InvalidData) ->

I don’t know if it is possible with the current syntax. What do you think?


#2

Explicit return statements are still very much valid. They are just not very common. :smile: Don’t be afraid to use them when you think they will make things easier to follow. The worst that may happen is that the function returns. An other alternative is restructuring the complex part, but that’s not always possible/easy/worth it. :wink:


#3

I find that very confusing. If those markers were optional, I see the danger of guiding the reader on the wrong path (like you do, try! does return, but on the first read, I only saw four lines returning).

I find this example easy to read anyways: The outer match expression is the result. The value of the outer match expression can be recursively gathered from the inner ones.

I think you are confusing a few things here. This line returns:

                        try!(Transport::validate_response_code(&buff, &reply[..]));

This one doesn’t:

                        Transport::get_reply_data(&reply, expected_bytes)

The first case immediately breaks out of the function in the error case and returns the error.

The second is the value of match self.stream.read(&mut reply), which is the value of match self.stream.write_all(&buff[..]) which is the value of (possibly the whole function or a let binding). It is not at all a return.

You should always read Rust code like this, except when a returning macro or an explicit return are present.