Function Declaration

func multiplyAdd(Float x, y, Int z) -> Float {
    return x * y  +  Float(z)
}

Function declarations start with the keyword func, as in Swift.
Easier parsing due to clear distinction between function declaration vs. variable declaration, avoiding the most vexing parse.

Function parameters are given as TypeName parameterName, multiple function parameters of the (exact) same type can be combined:
func multiply(Int x, y) -> Int // x and y are Int

Always and only in the trailing return type syntax (using -> ReturnType).
Void functions (AKA “procedures”) are written without trailing -> Void:
func print(String line) { ... }

Misc

Function pointers

Trying to maintain consistency between declarations of functions, function pointers, functors and lambdas.

Examples:

Function Parameter Passing Modes

Each function parameter in Cilia has a “parameter passing mode” that defines how its argument is passed and used — whether it’s input-only, mutable, output, copied, or moved.
The basic idea is to have the most efficient/appropriate parameter passing as the default, and to give more the intent than the technical realization.
Taken from Cpp2 / Herb Sutter (surely inspired by the out parameters of C#, and by Ada).

Parameter Passing Mode Keywords

Type Trait InParameterType

The type trait InParameterType determines the concrete type to be used for in-passing.

The rule of thumb is:

So, as general default, use const reference,

extension<type T> T {
  InParameterType = const T&
}

and use a “list of exceptions” for the “const value types”.

extension     Bool { InParameterType = const Bool }
extension     Int8 { InParameterType = const Int8 }
extension    Int16 { InParameterType = const Int16 }
extension    Int32 { InParameterType = const Int32 }
extension    Int64 { InParameterType = const Int64 }
extension    UInt8 { InParameterType = const UInt8 }
extension   UInt16 { InParameterType = const UInt16 }
extension   UInt32 { InParameterType = const UInt32 }
extension   UInt64 { InParameterType = const UInt64 }
extension  Float32 { InParameterType = const Float32 }
extension  Float64 { InParameterType = const Float64 }
extension   std::string_view { InParameterType = const std::string_view }
extension  std::span<type T> { InParameterType = const std::span<T> }
...

This way developers only need to extend this list if they create a small class (and if they need or want maximum performance). And I expect most custom classes to be larger than 16 bytes (so nothing to do for those).

extension<type T> Complex<T> { InParameterType = T::InParameterType }
extension  Complex<Float128> { InParameterType = const Complex<Float128>& }

Accordung to the generic rule, Complex<T> is passed the same way as T. But as sizeOf(Complex<Float128>) is 32 bytes (so pass by reference is desired), despite sizeOf(Float128) is 16 bytes (so pass by value would be the default), the generic rule is corrected.

Special Trick for Types with Views

Applicable only for types X that have an XView counterpart where

Like:

extension String { InParameterType = const StringView }
extension  Array { InParameterType = const ArrayView }
extension Vector { InParameterType = const VectorView }

So for an in String in fact a const StringView is used as parameter type. Then all functions with a String (AKA in String) parameter would implicitly accept

This way people do not necessarily need to understand the concept of a StringView. They simply write String and still cover all these cases.

Example

concat(String first, String second)

is short for concat(in String first, in String second)
and extends to concat(const StringView first, const StringView second).

Though I don’t see any advantage with respect to the for ... in loop, I would still apply the same rules just for consistency.

String[] stringArray = ["a", "b", "c"]
for str in stringArray {
  // str is a const StringView
}

This is not possible with every view type, as some views do not guarantee contiguous memory access (typically when they do support stride):

Small ...View-classes with a size of up to 16 bytes (such as StringView, ArrayView, and VectorView) will be passed by value:

extension String { InParameterType = const StringView }
extension  Array { InParameterType = const ArrayView }
extension Vector { InParameterType = const VectorView }

Bigger ...View-classes with a size of more than 16 bytes (such as MatrixBasicView, ImageBasicView, and MDArrayBasicView) will be passed by reference:

extension  Matrix { InParameterType = const MatrixBasicView& }
extension   Image { InParameterType = const ImageBasicView& }
extension MDArray { InParameterType = const MDArrayBasicView& }

But you do not have to write that explicitly, because const& simply is the standard for user defined types anyway.

Type Trait CopyParameterType

The type trait CopyParameterType of a type T typically is simply T itself:

extension<type T> T { CopyParameterType = T }

But for View-types the corresponding “full” type is used instead:

extension       StringView { CopyParameterType = String }
extension        ArrayView { CopyParameterType = Array }
extension       VectorView { CopyParameterType = Vector }
extension       MatrixView { CopyParameterType = Matrix }
extension  MatrixBasicView { CopyParameterType = Matrix }
extension        ImageView { CopyParameterType = Image }
extension   ImageBasicView { CopyParameterType = Image }
extension      MDArrayView { CopyParameterType = MDArray }
extension MDArrayBasicView { CopyParameterType = MDArray }

The idea is to get a mutable copy of the object, even without understanding the concept of a View.

Example

for copy str in ["an", "array", "of", "words"] { ... }

While the literal ["an", "array", "of", "words"] is a StringView[], str is a String (not a StringView).

This way people do not necessarily need to understand the concept of a StringView literal. They simply write copy to get a String with a copy of the content of the StringView.
(This is currently the only useful example I can think of.)