Argments vs tuple vs fixed array vs slice to pass fixed number of arguments?

When a function require same type and fixed number of arguments, there are following ways.

fn way1(x0: T, x1: T, x2: T)
fn way2(xs: (T, T, T))
fn way3(xs: &(T, T, T))
fn way4(xs: [T; 3])
fn way5(xs: &[T; 3])
fn way6(xs: &[T]) // and assert_eq!(xs.len(), 3)

What's the pros and cons for these?

It depends on the context. Do each of your items make sense as part of a homogeneous collection, or are they meant to be used separately?

For example say I need to find the arc that passes through three points, the arc's start and end points, and some random point along the arc that isn't an endpoint.

I'd keep the arguments separate:

fn calculate_arc(start: Point, end: Point, middle: Point) -> Arc { ... }

But say I'm calculating the smallest rectangle that can fit around a pentagon. For all intents and purposes, I don't care whether a point is the 2nd or 4th corner, just that it's a point.

In this case the points don't have any individual significance or meaning, so I'd pass them in using some homogeneous collection (i.e. array/slice). I would prefer a slice over an array because that lets me reuse the same function for any number of points (a line, triangle, dynamically sized polyline, point cloud, etc.).

struct BoundingBox { ... }

fn bounding_box_for_points(points: &[Point]) -> BoundingBox {
  let mut bounding_box = BoundingBox::empty();
  
  for point in points {
    bounding_box = bounding_box.merged_with(point);
  }

  bounding_box
}

Accepting a slice does require you to already have the items in an array or Vec (or that you copy them to a temporary one for the calculation), but if items don't have individual meaning you're probably storing them that way anyway.

Totally agree. The question is are all those values meaningfully related in such away that one might always (mostly) use them together, in which case one might think of them all as a list (Vec, slice), struct or Vec. Or are they just a random assortment of otherwise unrelated values that the function happens to use as parameters, in which case they might deserve to stand in their own right as meaningfully named parameters.

Oddly though I might disagree about your calculate arc example. That calculation need not care which point is which among start, middle and end. The result is always the same. So they could all as well be elements of a Vec/slice.

If we were trying to calculate a circle you would be right, but with an arc it very much depends on the order. If you draw it out on paper, you'll see you get different arcs depending on the order of points you draw the arc through.

Most of the arcs I work with also have a direction because they specify movements through physical space or have some sort of connected-ness/ordering constraint (e.g. end of line A needs to be the start of arc B).

It just goes to show that even within a similar problem (arc from 3 points), the way you interpret the arguments can change depending on your context.

Oddly enough, immediately after writing that I started drawing it out on paper to see if I was wrong :slight_smile:

Directivity is there naturally as the order of the points as elements in Vec parameter.

One might naturally represent a polyline as an array of points, as in the Win32 API: https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-polyline or the polyline in SVG: https://www.w3schools.com/graphics/svg_polyline.asp

So why not an arc?

Thanks. Whether if it is homogeneous makes sense for me.

How about matters which never be reusable, like area of a triangle from length of lines?
Should I use slice before assertion cost become critical?