I think the explanation of *Array in the documentation is a bit too abstract, and I could not find any explanation of arg ... or |...|. So I wrote a few simple examples below.
// Use ... when the inputs are not fixed in advance,
// either in number or in keywords.
// Any extra arguments are collected into one array.
// All input values go into rest.
{ arg ... rest; rest }.(1, 2, 3, 4, 5)
-> [1, 2, 3, 4, 5]
// The same, using pipe syntax.
{ |... rest| rest }.(1, 2, 3, 4, 5)
-> [1, 2, 3, 4, 5]
// a gets the first value.
// All remaining values go into rest.
{ arg a ... rest; [a, rest] }.(1, 2, 3, 4, 5)
-> [1, [2, 3, 4, 5]]
// The same, using pipe syntax.
{ |a ... rest| [a, rest] }.(1, 2, 3, 4, 5)
-> [1, [2, 3, 4, 5]]
// a gets 1, b gets 2.
// All remaining values go into rest.
{ arg a, b ... rest; [a, b, rest] }.(1, 2, 3, 4, 5)
-> [1, 2, [3, 4, 5]]
// The same, using pipe syntax.
{ |a, b ... rest| [a, b, rest] }.(1, 2, 3, 4, 5)
-> [1, 2, [3, 4, 5]]
// Commas may be omitted here.
{ |a b ... rest| [a, b, rest] }.(1, 2, 3, 4, 5)
-> [1, 2, [3, 4, 5]]
// A function with default values.
f = { |a=0, b=1| a + b }
f.(1, 2)
-> 3
// The same call, using argument keyword.
f.(a: 1, b: 2)
-> 3
// The same call, by expanding an array into arguments.
f.(*[1, 2])
-> 3
// Different: the whole array is passed as a.
f.([1, 2])
-> [2, 3]