Function argument/parameter indentation


#21
fn get_host_addresses(
    &mut self,
    host: Option<&str>,
    servname: Option<&str>,
    hint: Option<AddrinfoHint>
) -> IoResult<Vec<AddrinfoInfo>> {
    iocpabort!("nope")
}

I find this is much better than the following, which I find horrible

fn get_host_addresses(&mut self,
                      host: Option<&str>,
                      servname: Option<&str>,
                      hint: Option<AddrinfoHint>)
                      -> IoResult<Vec<AddrinfoInfo>> {
     iocpabort!("nope")
}

#22

When using multiple lines, I go for thorough visual indent, and line the opening <, ( and -> up, with the effect that the -> is one space back from how you’ve shown it there:

fn get_host_addresses(&mut self,
                      host: Option<&str>,
                      servname: Option<&str>,
                      hint: Option<AddrinfoHint>)
                     -> IoResult<Vec<AddrinfoInfo>> {

#23

This is my preferred style as well. Others have pointed out that changing the function name causes “unnecessary noise” in diffs, which I agree with, but don’t particularly care about myself. The biggest problem is that adding many generics feels like it gets a bit lost (but it’d look nicer with where clauses).


#24

Forgive me if I’m being rude by repeating myself but I think that multi-line invocations must not use the following form:

get_bar(arg1,
        arg2,
        arg3,
        arg4);

If get_bar would be renamed then that would require a lot of realigning for all callers, which I think is unacceptable. As the length of lines is limited by the style guide then perhaps this realignment might also cause cascades of rewrapping following the invocations.

Please give an argument against this if you prefer the invocation style above (currently in the style guide).

Wouldn’t it be preferred that multi-line fn definitions and invocations shared the same indentation style?


#25

My argument against this is is “who cares”. rustfmt would just realign as necessary, and doing it by hand is extremely simple, just a block insert/delete of a certain amount of newlines etc. I find it a total non-issue. I understand the disagreement behind this though.


#26

It’s more of an issue for things like git blame than the actual act of reindenting itself (which is pretty trivial in most editors). That said, that’s not really my primary issue with it - I just find it harder to skim over code when everything is indented at random meaningless levels.


#27

Commit diffs will be polluted by changes that do not pertain to the essential change.

There was also another proposal to allow an optional comma at the end of comma-delimited lists, with the same motivation.

Suppose you have:

bar = get_bar(arg1,
              arg2,
              arg3,
              arg4);

…and you want to modify the lhs of the assignment. Again, unrelated changes would follow. I think there’s a merit in avoiding unrelated changes as much as possible, especially when you’re in the position of designing the recommended/standard formatting guidelines.


#28

Again, I could not care less about “diff pollution”. It’s entirely a non-issue. Every modern tool I know of even knows how to ignore whitespace-only changes.


#29

Ignoring whitespace is not the default (and it’s very non-obvious how to do it in something like a Github pull request), and it can be prohibitively expensive on large repositories for operations like git blame. It’s true that you can work around it in common cases, but it does still have a cost. Again, though, I don’t think this is really the most important aspect of the issue. Being able to skim code easily based on indentation and avoiding rightward drift are more important, in my opinion.


#30

Does git blame support that?


#31

Yes, git blame -w.


#32

For this and its variations:

foo<T,
    U>(
    bar,
    baz)
    -> qux {
    /* ... */
}

we have habit and personal preference vs rightward drift, hard-to-see generics and method body, and diff churn.

For this:

foo<
    T, U
>(
    bar,
    baz
) -> qux {
    /* ... */
}

we have consistency; shorter lines; a more visible split between generics, arguments, and the method body; and less reliance on tooling support or messing with indentation vs personal preference.

Did I miss something?


#33

Yes, that some people (not me) for some reason think the latter looks ugly. Also, that it shares the same indentation style as regular code blocks which again some people for some reason think is bad.


#34

I also prefer to use single indentation for scope and a double indent for line continuations, sometimes with an additional indent for clarity if there is nesting. I want all of my indents to be a multiple of four. So, I agree with these:

frobnicate(arg1, arg2,
        arg3, arg4);

let really_really_really_long_name =
        "really really really long value too";

And would add this:

let result = value1 * value2
        * (value3 + value4    // Double indent for continuation
            + value5 + value6)  // Extra indent for nesting clarity
        * value7;

And for the function definition, I would do this:

fn some_func<T: This, U: That,
            V: SomeMore, W: EvenMore>
        (arg1: Type1, arg2: Type2, arg3: Type3,
            arg4: Type4, arg5: Type5)
        -> ReturnType {
    // ...
}

#35

I would like to add my thoughts to the ongoing style discussion, for i sadly lack the technical expertise to contribute in a more meaningful way, but would love to help nevertheless.

After comparing some style-guides (linux, gnu, K&R, …) for curly braced languages like C, JavaScript, etc., and some common typographic practices i tried some variations and found some (imho) reasonable and readable solutions.


Most trivial functions (no arguments, no return value):

fn trivial() {
   // no arguments, no return value -> K&R style as for control blocks
   // function comments "python style" between function-"head" and -"body"
}

Simple functions (few arguments, no return value)

fn simple(arg1:u8, arg2:u8)
{
   // arguments or return value -> K&R style for functions (as linux kernel)
   // 
   // arguments on same line separated by comma+space, therefore no space
   // between colon and type, to improve semantic visual contrast;
}

Regular function (few arguments, return value)

fn regular(arg1:u8, arg2:u8)
-> u8
{
   // return value on separate line with same identation as function head
   // rational: visual pattern allows direct association of name and type
}

Irregular long argument-list (factory function, etc.)

fn many_arguments(
   arg1: u8,    // incredible amount of space for comments for every ...
   arg2: u8,    // ... variable (but there's never enough)
   ...
   argn: u8  
   )
-> u8
{
   // argument identifiers are now in the same column as the function 
   // identifier, but it's still unmistakably clear that they are argument
   // identifiers because of the leading whitespace.
   // the closing parenthesis of the argument list gets its own line to
   // visually segregate the argument list from the return type
   // the opening brace is a strong visual pattern to separate function-head
   // from function-body
   // a logical conclusion of this style is, that a tab-width of
   // three spaces *gasp*, *Blasphemy!* seems logical. It's clearly an 
   // unorthodox choice, but i found it quite easy on the eyes. And except
   // breaking tradition i found no reasonable argument against three spaces.
} 

Regular function with generic types

fn generics <T, U, V> (
   a: &T,
   b: &U
   )
-> V
{
   // function with generic types should add the generics on the same line,
   // followed by the opening argument parenthesis and a newline with the
   // short or long argument-list rules.
   // "first-glance" rule reads:
   //    glance 1: "Generic function named 'generics' with 3 generic types."
   //    glance 2: "Return type is the generic type 'V' ."
   //    glance 3: "Two arguments."
   //    glance 4: "First argument 'a' with generic type 'T'."
   //    glance 5: "Second argument 'b' with generic type 'U'."
}

Short version:

fn generics_short <T> (
   a:&T, b:u8) 
{
   // example of a short generic function.       
}

Principles:

  • do one thing per line or use perl
  • try to stay inside typographic 60 glyphs per line, which is the suggested column width for e.g. newspapers.
  • create visual patterns to facilitate easy semantic readings (e.g. function name and associated return value in same column)
  • visually segregate unassociated units and couple associated units (e.g. no space between name and type in an one-line argument-list to improve visual distinction between arguments)
  • try to uphold the “first-glance” rule: the reader should discern every line as one semantic/logical unit without the need to parse it, which essentially means reading it multiple times and searching for start and stop delimiters of every semeantic part. (e.g.: “Where does the generic type list end, where are the arguments, where is the return type?”)

Example on servo-code: original:

pub extern "C" fn android_start(argc: int, argv: **u8) -> int {
..
}

vs “first-glance-style”:

pub extern "C"          
fn android_start(argc:int, argv:**u8)  
-> int                                   
{
   ..
}

Off-topic i would argue to separate function-prefixes like pub and extern on a separate line, like python decorators.:

pub extern "C"
fn many_arguments(
   arg1: u8,    // incredible amount of space for comments per variable
   arg2: u8,    // you can either use incredible long meaningful identifiers
   ...
   argn: u8     // or write useful comments
   )
-> u8
{
} 

#36

The current style guide more or less enforces the use of monospaced fonts. It seems a bit backwards to me.

Here’s a promising family of proportional fonts for coding. Check them out: http://input.fontbureau.com/info/


#37

Using a proportional font requires fairly intelligent editor support if you’re going to have comments lining up as well. In Go, gofmt can line up comments, assuming monospaced fonts, so something equivalent to gofmt (AST-based reformatting) would likely need to be built into the editor.

This brings us back to AST-based editors, which would make all of these syntax arguments go away (i.e. most of the traffic on this forum!), because everyone could look at the code with their own favourite syntax style, e.g. {} or python-style blocks, <> or [] for templates, whatever.

I did make a start on an AST-based editor for Java a few years ago, but unfortunately I didn’t get it finished before starting a new contract. I did write up some of my ideas here, though: http://uazu.net/notes/astlang.html


#38

I think that the notion of AST-based editors is fabulous: The compiling could be executed as you were typing, relevant unit tests could be run as well, etc…

But I think we should require neither smart editors nor monospaced fonts.


#39

I also dislike the current style, for all the reasons listed in this thread.

One option I did not see while skimming this thread is to do what the java style guide recommends: Just add a linebreak and a indentation for a long function call, and a linebreak and two intendations for a long function signature:

{
    let x = foo(value_a, value_b,
        value_c);
    let y = foo(value_a, value_b,
        value_c);
    bar();
}
fn foo(long_arg1: T, long_arg2: T, 
        long_arg3: T) -> LongType {
    /* body */
}
fn foo(long_arg1: T, long_arg2: T, 
        long_arg3: T, long_type4: T) 
        -> LongType {
    /* body */
}

Advantages:

  • Renamings never cause uneccessary line diffs
  • Scales better in the nested cases, due to constant-width intentation increments
  • Function signature is still visually seperated from function body by a intendation level
  • Dead simple rule, can be consistenly applied manually even in the face of refactors.
  • Plays better with simple editors; I regularly have Rusts current style being broken by editor smarts that are only intended for this kind of fomatting.

Apart from this option though, I’d prefer @tbu 's style as well.


#40

I’m using a similar style at the moment, with slight variations. Consider this:

fn foo <T: Clone + Hash + Add + Eq,
        U: Copy + Send, >( 
   thing: &T
   )
-> U
{
    // ...
}

Rationale for indentation, braces and parenthesis placement:

I like K&R undogmatic, pragmatic approach where they use a different braces style for control structures (opening brace on same line) and functions (opening brace on separate line). They had good cause for these choices.

As such i would suggest to keep the K&R style and move the opening brace on a separate line. (Except for very trivial functions with no arguments and no return value)

fn frobnicate(a: Bar)
{
   // Comment rather inside the function then above the definition.
}

A style is mostly visual and should facilitate semantic comprehension. As such we should try to alleviate the reading flow and thus enable the human visual system to quickly discern visual(=semantic) patterns.

As your example:

fn frobnicate(
    a: Bar, b: Bar,
    c: Bar, d: Bar,
) -> Bar {
    // ...
}

I believe we could enlighten the burden on the human pattern recognition system with two things:

  1. The return type is associated with the function name and should therefore not share the same level(=indentation) as the subordinated function-arguments.:

    fn frobnicate( a: Bar, b: Bar, c: Bar, d: Bar, ) -> Bar { // comment } This way we always see at first glance what the type of a function is and don’t need to search for the -> Type assignment.

  2. If we are in the unfortunate situation of to many, or to long arguments we need to split line. I like your approach of beginning a new line, but would suggest to be even more radical and only place one argument per line.:

    fn frobnicate( a: Bar, b: Bar, c: Bar, d: Bar ) -> Bar { // comment }

This way you either have a flow from left to right (single-line arguments) or from up to down. Furthermore i would suggest to place the closing parenthesis also on a separate line to increase the distinction from the return type, especially as in this example the return type is the same as of the last argument.


I have even a rationale for the generics part, and the >( on the same line but would like to hear your thoughts on this first.