Skip to main content

[From C# to Swift] 06. Functions

Learning Swift from a C# Perspective

Swift : Functions

Defining and Calling Functions
#

1. Core Concepts
#

  • Concept Explanation: A function is a self-contained block of code that performs a specific task. In Swift, function syntax is highly flexible, supporting everything from a simple C-style to an Objective-C style with detailed argument labels. Every function has a type, consisting of the parameter types and the return type.
  • Key Syntax: func, ->, return
  • Official Note:

Note The print(_:separator:terminator:) function doesn’t have a label for its first argument, and its other arguments are optional because they have default values. These syntax variations are discussed in detail below.

2. Example Analysis
#

Document Source Code:

func greet(person: String) -> String {
    let greeting = "Hello, " + person + "!"
    return greeting
}

print(greet(person: "Anna"))
// Prints "Hello, Anna!"

Logic Explanation: This code defines a function named greet that takes a parameter person of type String and returns a String. The -> symbol is used to indicate the return type. When calling the function, the argument label person: "Anna" must be included.

3. C# Developer’s Perspective
#

Concept Mapping: This is equivalent to a Method definition in C#.

C# Comparison Code:

public string Greet(string person) {
    string greeting = "Hello, " + person + "!";
    return greeting;
}

// Call
Console.WriteLine(Greet("Anna")); // Usually argument names are not enforced
// Or use named arguments
Console.WriteLine(Greet(person: "Anna"));

Key Difference Analysis:

  • Syntax: Swift uses the func keyword, and the return type is written after the arrow ->; C# places the return type before the method name.
  • Behavior: The most significant difference lies in calling conventions. Swift defaults to requiring Argument Labels at the call site, making the code read like a sentence; while C# supports Named Arguments, they are not enforced by convention.

Function Parameters and Return Values
#

1. Core Concepts
#

  • Concept Explanation: Swift parameters and return values are very flexible. It supports no parameters, multiple parameters, no return value (effectively returning Void), and multiple return values via Tuples.
  • Key Syntax: Void, Tuple, Optional Tuple
  • Official Note:

Strictly speaking, functions without a defined return type still return a value. They return a special value of type Void, which is simply an empty tuple, written as ().

2. Example Analysis
#

Document Source Code (Multiple Return Values):

func minMax(array: [Int]) -> (min: Int, max: Int)? {
    if array.isEmpty { return nil }
    var currentMin = array[0]
    var currentMax = array[0]
    for value in array[1..<array.count] {
        if value < currentMin {
            currentMin = value
        } else if value > currentMax {
            currentMax = value
        }
    }
    return (currentMin, currentMax)
}

Logic Explanation: This example demonstrates how to use a Tuple to return both a maximum and minimum value at once. Additionally, the return type is marked as (min: Int, max: Int)?, indicating it is an “Optional Tuple”, allowing nil to be returned when the array is empty.

3. C# Developer’s Perspective
#

Concept Mapping: This is very similar to ValueTuple introduced in C# 7.0.

C# Comparison Code:

// C# 7.0+ Tuple Syntax
public (int min, int max)? MinMax(int[] array) {
    if (array.Length == 0) return null;
    
    int currentMin = array[0];
    int currentMax = array[0];
    
    foreach (var value in array.Skip(1)) {
        if (value < currentMin) currentMin = value;
        else if (value > currentMax) currentMax = value;
    }
    return (currentMin, currentMax);
}

Key Difference Analysis:

  • Syntax: The syntax is strikingly similar. Swift’s Void corresponds to C#’s void, but Swift’s Void is essentially an empty Tuple (), whereas C#’s void is not a type that can be passed around.
  • Behavior: Note that Swift’s Optional Tuple (Int, Int)? means the entire Tuple might not exist; this is different from a Tuple containing Optionals (Int?, Int?). C#’s Nullable<(int, int)> behaves similarly.

Functions With an Implicit Return
#

1. Core Concepts
#

  • Concept Explanation: If the entire body of the function is a single expression, the return keyword can be omitted.
  • Key Syntax: Implicit Return

2. Example Analysis
#

Document Source Code:

func greeting(for person: String) -> String {
    "Hello, " + person + "!"
}

Logic Explanation: The compiler automatically treats the result of the last line as the return value, making the code more concise.

3. C# Developer’s Perspective
#

Concept Mapping: Equivalent to C#’s Expression-bodied members.

C# Comparison Code:

public string Greeting(string person) => "Hello, " + person + "!";

Key Difference Analysis:

  • Syntax: C# uses the lambda arrow =>; Swift keeps the braces {} but omits return.
  • Behavior: The concepts are identical; both aim to reduce boilerplate code.

Function Argument Labels and Parameter Names
#

1. Core Concepts
#

  • Concept Explanation: This is one of Swift’s most distinctive designs. Every parameter has two names:
    1. Argument Label: Used when calling the function, to make the call site read like natural language.
    2. Parameter Name: Used inside the function implementation.
  • Key Syntax: ArgumentLabel ParameterName: Type, _ (omit label)

2. Example Analysis
#

Document Source Code:

// Specify label: use 'from' for calling, 'hometown' internally
func greet(person: String, from hometown: String) -> String {
    return "Hello \(person)!  Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))

// Omit label: use _
func someFunction(_ firstParameterName: Int, secondParameterName: Int) { ... }
someFunction(1, secondParameterName: 2)

Logic Explanation: The greet function uses from to make the call greet(..., from: "Cupertino") read very fluently. If you do not wish to enforce a label, you can use an underscore _ to omit it, which is useful when mimicking C-style libraries.

3. C# Developer’s Perspective
#

Concept Mapping: C# does not have a direct “internal vs external” naming mechanism. C# parameter names are used for both internal implementation and named argument calls.

C# Comparison Code:

// C# cannot completely simulate Swift's "from hometown" syntax
// Can only try to express semantics via parameter naming
public string Greet(string person, string from) {
    return $"Hello {person}! Glad you could visit from {from}.";
}

// Call
Greet(person: "Bill", from: "Cupertino");

Key Difference Analysis:

  • Syntax: Swift allows func f(label name: Type), C# only has void f(Type name).
  • Design Philosophy: Swift emphasizes Call-site readability, treating “reading like an English sentence” as a primary guiding principle (a legacy from Objective-C). C# developers might initially find writing an extra label redundant, but after getting used to it, they often find it significantly aids self-documenting code.

Default Parameter Values
#

1. Core Concepts
#

  • Concept Explanation: You can provide default values for parameters when defining a function. If that parameter is omitted during the call, the default value is used.
  • Key Syntax: param: Type = value

2. Example Analysis
#

Document Source Code:

func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
    // ...
}
someFunction(parameterWithoutDefault: 4) // parameterWithDefault uses 12

3. C# Developer’s Perspective
#

Concept Mapping: Fully corresponds to C#’s Optional Arguments.

C# Comparison Code:

void SomeFunction(int parameterWithoutDefault, int parameterWithDefault = 12) { ... }

Key Difference Analysis:

  • Behavior: The mechanisms are almost identical. However, in Swift, it is generally recommended to place parameters without default values first, and those with default values later (though Swift syntax doesn’t strictly enforce this, it is a good practice), which aligns with C#’s rule (optional parameters must appear after required parameters).

Variadic Parameters
#

1. Core Concepts
#

  • Concept Explanation: Allows a function to accept zero or more values of a specific type. Currently, only one Variadic parameter is allowed, and it must be placed at the end of the parameter list.
  • Key Syntax: Type...

2. Example Analysis
#

Document Source Code:

func arithmeticMean(_ numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)

Logic Explanation: Inside the function, numbers is treated as a [Double] array.

3. C# Developer’s Perspective
#

Concept Mapping: Corresponds to the params keyword in C#.

C# Comparison Code:

public double ArithmeticMean(params double[] numbers) {
    double total = 0;
    foreach (var number in numbers) {
        total += number;
    }
    return total / numbers.Length;
}

Key Difference Analysis:

  • Syntax: Swift uses the suffix ..., while C# uses the prefix params keyword.
  • Behavior: Swift currently allows only one Variadic parameter, and it generally must be placed at the end of the parameter list; C# has similar restrictions.

In-Out Parameters
#

1. Core Concepts
#

  • Concept Explanation: By default, Swift function parameters are Constants and cannot be modified inside the function. If you need to modify the passed variable and have the changes reflected back to the original variable, use inout.
  • Key Syntax: inout, &
  • Official Note:

In-out parameters cannot have default values, and variadic parameters cannot be marked as inout.

2. Example Analysis
#

Document Source Code:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)

Logic Explanation: The inout keyword indicates the parameter can be modified. When calling, the & symbol must be added before the variable, explicitly indicating that a reference to the memory address/value is being passed.

3. C# Developer’s Perspective
#

Concept Mapping: Corresponds to the ref keyword in C#.

C# Comparison Code:

public void SwapTwoInts(ref int a, ref int b) {
    int temporaryA = a;
    a = b;
    b = temporaryA;
}

int someInt = 3;
int anotherInt = 107;
SwapTwoInts(ref someInt, ref anotherInt);

Key Difference Analysis:

  • Syntax: Swift uses inout in the definition and & in the call; C# uses ref in both definition and call.
  • Behavior: The concepts are consistent; both allow Value Types (like Struct, Int) to be modified like Reference Types. Note that Swift’s inout behavior acts as “Copy-in Copy-out” (or Call by value result) in low-level optimization, which may have subtle differences from C#’s direct memory location reference (ref) in multi-threaded environments, but they can be treated as the same in general usage.

Function Types
#

1. Core Concepts
#

  • Concept Explanation: In Swift, functions are First-class citizens. Functions themselves have types and can be assigned to variables, passed as parameters, or used as return values.
  • Key Syntax: (ParamTypes) -> ReturnType

2. Example Analysis
#

Document Source Code:

func addTwoInts(_ a: Int, _ b: Int) -> Int { a + b }

// Define variable mathFunction, type is "function accepting two Ints and returning Int"
var mathFunction: (Int, Int) -> Int = addTwoInts
print("Result: \(mathFunction(2, 3))")

// Function as parameter
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
    print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)

Logic Explanation: The mathFunction variable stores a reference to the addTwoInts function. This allows you to dynamically swap execution logic.

3. C# Developer’s Perspective
#

Concept Mapping: Corresponds to C#’s Delegates (Func<>, Action<>).

C# Comparison Code:

int AddTwoInts(int a, int b) => a + b;

// Using Func delegate
Func<int, int, int> mathFunction = AddTwoInts;
Console.WriteLine($"Result: {mathFunction(2, 3)}");

// Function as parameter
public void PrintMathResult(Func<int, int, int> mathFunc, int a, int b) {
    Console.WriteLine($"Result: {mathFunc(a, b)}");
}

Key Difference Analysis:

  • Syntax: Swift’s syntax (Int, Int) -> Int is more intuitive and readable than C#’s Func<int, int, int>.
  • Behavior: Essentially the same; both are references to code blocks. Swift’s syntactic sugar makes Functional Programming patterns easier to accept and implement.

Nested Functions
#

1. Core Concepts
#

  • Concept Explanation: You can define a function inside another function. The inner function can access variables from the outer function (Closure property) and can be returned for external use.
  • Key Syntax: Function scope

2. Example Analysis
#

Document Source Code:

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    func stepForward(input: Int) -> Int { return input + 1 }
    func stepBackward(input: Int) -> Int { return input - 1 }
    return backward ? stepBackward : stepForward
}

Logic Explanation: stepForward and stepBackward are hidden inside chooseStepFunction. They cannot be called directly from the outside, keeping the global namespace clean. However, they can be passed out as return values.

3. C# Developer’s Perspective
#

Concept Mapping: Corresponds to Local Functions introduced in C# 7.0.

C# Comparison Code:

public Func<int, int> ChooseStepFunction(bool backward) {
    int StepForward(int input) => input + 1;
    int StepBackward(int input) => input - 1;
    
    return backward ? (Func<int, int>)StepBackward : StepForward;
}

Key Difference Analysis:

  • Syntax: The structure is almost identical.
  • Behavior: Both support Closures, meaning the inner function can capture variable states from the outer function. This effectively encapsulates helper functions when writing complex algorithms or recursive logic.