Code style question: position for pass-thru/context arguments

Hi! Often, a function has several essential arguments, and one or two "context" arguments. The most common example here is graph traversal, where essential argument is the vertex, and pass thru context is the graph and the set of already processed vertices.

My question is, should context be the first argument, or the last argument? I think about this every single time and haven't reached any definitive conclusion yet :frowning:

I believe this was debated a bit in the context of futures, namely the new &mut Context arg that is passed to the various poll_xxx methods. Some of the opinions are expressed in this thread, although I feel like I saw it elsewhere as well (but can't find it). That particular thread's tl;dr is likely this comment. I probably agree with the "closure argument" aspect as well.

3 Likes

I always think of "Context Arguments" as somewhat ancillary. If we were doing OOP, they would be fields of the Class that the method you are calling is on and wouldn't be passed to the function. If you were doing Haskell-like functional programming, you would curry a function with the context parameters first, and then call the curried function with the arguments that were varying/mattered. I think this aligns with the "Closure Argument" that @vitalyd is alluding to.

That being said, if we are creating a function that takes context arguments for some needed reason, then, I would think, them being ancillary/contextual, they should be given first, after self so the context is clearly apparent. But, I think it could easily be argued either way.

I think a good argument for making them first is that if you were doing the currying/functional solution, you would curry first. If you were doing the OOP solution, you would set the fields first. So, if you're doing this solution, you set the context parameters first. That would be my argument anyway.

5 Likes

+1 to what @gbutler69 says.

I like to keep parameters that are contextual to the left, and parameters that are more likely to vary (or have sensible defaults) to the right. That way, when the function is called multiple times, you can see the difference better:

// if it's to the left, `context`'s alignment jumps around
my_function("search_string_1", context);
my_function("thing", context);
my_function("abcdefasd", context);

// vs
my_function(context, "search_string_1");
my_function(context, "thing");
my_function(context, "abcdefasd");

Also, for languages that support overloading, it lets you do this:

my_function(context, "param");
my_function(context, "param", more_stuff);
my_function(context, "param", more_stuff, even_more_non_defaults);
9 Likes

Yes, I agree. This is another important argument for the benefit of keeping them first. I forgot about this. With this I take back my comment about it can kinda be argued either way. I think this, together with the previous rationale about fields/currying, the case is closed. (IMHO)

1 Like

I put "most important" (i.e. the function absolutely couldn't exist without it) argument first.

I generally prefer the larger objects first or the ones that are more closely related to the function first. Which generally means that loose strings and integers are at the end.