Skip to main content

[From C# to Swift] 01. The Basics

Learning Swift from a C# Perspective

Swift : The Basics

Constants and Variables
#

1. Core Concepts
#

  • Concept Explanation: Swift places a heavy emphasis on safety; therefore, when declaring storage containers, it strictly distinguishes between “Immutable” and “Mutable.” This helps the compiler optimize and prevents accidental data modification.
  • Key Syntax: let (constant), var (variable)
  • Official Tip:

If a stored value in your code won’t change, always declare it as a constant with the let keyword. Use variables only for storing values that need to be able to change.

2. Example Analysis
#

Documentation Source Code:

let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

var environment = "development"
let maximumNumberOfLoginAttempts: Int
// maximumNumberOfLoginAttempts does not have a value yet

if environment == "development" {
    maximumNumberOfLoginAttempts = 100
} else {
    maximumNumberOfLoginAttempts = 10
}
// Now maximumNumberOfLoginAttempts has a value and can be read

Logic Explanation: This code snippet demonstrates the basic declaration of let and var. It is worth noting that a constant declared with let does not necessarily need to be assigned a value on the same line it is declared (as shown in the second example), but it must be guaranteed to be “assigned once and only once” before it is first read.

3. C# Developer’s Perspective
#

Concept Mapping:

  • Swift’s var corresponds to C#’s var (or explicitly typed variables).
  • Swift’s let conceptually sits between C#’s const and readonly. However, regarding local variables, C# does not have a completely equivalent keyword for a “runtime immutable local variable” (although C#’s const must be a compile-time constant).

C# Comparison Code:

// C#
const int MaximumNumberOfLoginAttempts = 10; // Value must be determined at compile time
var currentLoginAttempt = 0;

// C# simulating Swift's delayed initialization constant (C# cannot directly enforce read-only checks on local variables)
int maxLoginAttempts; 
var environment = "development";

if (environment == "development") {
    maxLoginAttempts = 100;
} else {
    maxLoginAttempts = 10;
}
// In C#, maxLoginAttempts can still be modified later; this is the difference in safety

Key Difference Analysis:

  • Syntax: Swift uses two keywords, let and var, to govern all declarations; C# often mixes var, const, readonly, and explicit types.
  • Behavior: Swift’s let allows “Runtime evaluation,” as long as it is guaranteed to be assigned exactly once. C#’s const must be a “Compile-time constant.” This makes Swift’s let more flexible and more commonly used than C#’s const.

Type Safety and Inference
#

1. Core Concepts
#

  • Concept Explanation: Swift is a Type Safe language. You can explicitly tell the compiler what type a variable is (Type Annotation), or let the compiler automatically guess based on the initial value (Type Inference).
  • Key Syntax: : (colon followed by type), String, Int, Double

2. Example Analysis
#

Documentation Source Code:

var welcomeMessage: String
welcomeMessage = "Hello"

var x = 0.0, y = 0.0, z = 0.0 // Inferred as Double

let meaningOfLife = 42 // Inferred as Int
let pi = 3.14159 // Inferred as Double

Logic Explanation: When no initial value is provided (like welcomeMessage), you must explicitly write : String. If there is an initial value (like meaningOfLife), the compiler will infer it automatically.

3. C# Developer’s Perspective
#

Concept Mapping: This is almost identical to the C# type system.

C# Comparison Code:

// C#
string welcomeMessage;
welcomeMessage = "Hello";

var x = 0.0; // Inferred as double
var meaningOfLife = 42; // Inferred as int

Key Difference Analysis:

  • Syntax: In Swift, the variable name comes before the colon and the type comes after (name: Type); in C#, the type comes first (Type name).
  • Behavior: The inference mechanisms (Swift’s Type Inference vs. C#’s var) are very similar. However, Swift is stricter about type conversion (no implicit conversion allowed at all, as detailed below).

Numeric Types and Conversion
#

1. Core Concepts
#

  • Concept Explanation: Swift provides numeric types such as Int, UInt, Double, and Float. Note particularly that Swift does not support “Implicit Conversion” between different numeric types; even putting an integer into a floating-point variable requires explicit conversion.
  • Key Syntax: Int.min, Int.max, Double(...), Int(...)
  • Official Tip:

Use Int for all general-purpose integer constants and variables in your code, unless you need a specific size of integer (e.g., handling external data). Even on 32-bit platforms, Int is sufficient to cover the range from -2 billion to 2 billion.

2. Example Analysis
#

Documentation Source Code:

let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one) // Must be manually converted

let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine // Integer to floating-point

Logic Explanation: Swift does not allow adding a UInt8 directly to a UInt16, nor does it allow adding an Int directly to a Double. You must use a constructor (like UInt16(one)) for conversion, which essentially creates a new value.

3. C# Developer’s Perspective
#

Concept Mapping: C# allows implicit behavior for “Widening Conversions,” but Swift prohibits this entirely.

C# Comparison Code:

// C#
ushort twoThousand = 2000;
byte one = 1;
var result = twoThousand + one; // C# performs automatic implicit conversion, legal

int three = 3;
double pointOne = 0.14159;
var pi = three + pointOne; // C# automatically converts int to double, legal

Key Difference Analysis:

  • Syntax: Swift uses constructor syntax Type(value) for conversion; C# uses casting syntax (Type)value or Convert.ToType(value).
  • Behavior: This is the most common compilation error for C# developers. C# is used to int + double automatically becoming double, but in Swift, this throws an error. Swift’s design philosophy considers implicit conversion a breeding ground for bugs.
  • Type Details:
    • Swift’s Int depends on the platform (it is 64-bit on a 64-bit platform).
    • C#’s int is always 32-bit (Int32). Only the nint introduced in C# 9.0 is equivalent to Swift’s Int.

Tuples
#

1. Core Concepts
#

  • Concept Explanation: Tuples allow you to group multiple values into a single compound value. They are ideal for returning multiple results from a function without needing to create a new Struct or Class.
  • Key Syntax: (Val1, Val2), .0, .1, (name: Val)

2. Example Analysis
#

Documentation Source Code:

let http404Error = (404, "Not Found")
let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")

// Access via index
print("The status code is \(http404Error.0)")

// Named elements
let http200Status = (statusCode: 200, description: "OK")
print("The status code is \(http200Status.statusCode)")

Logic Explanation: Tuples can be decomposed into individual constants or accessed via index (.0) or label names (.statusCode).

3. C# Developer’s Perspective
#

Concept Mapping: Corresponds to modern C# (C# 7.0+) ValueTuple.

C# Comparison Code:

// C#
var http404Error = (404, "Not Found");
var (statusCode, statusMessage) = http404Error; // Deconstruction

// Named elements
var http200Status = (StatusCode: 200, Description: "OK");
Console.WriteLine(http200Status.StatusCode);

Key Difference Analysis:

  • Syntax: The syntax for both is strikingly similar.
  • Behavior: Both Swift Tuples and C# ValueTuples are Value Types. However, Swift’s official recommendation is to use Tuples only for temporary data groupings (like function returns). If the data structure is complex, you should switch to a struct or class.

Optionals
#

1. Core Concepts
#

  • Concept Explanation: This is Swift’s most important feature. An Optional represents “there is a value,” or “there is no value at all (nil).” Swift’s standard types (like Int, String) can never be nil. To handle missing values, you must add a ? to make it an Optional.
  • Key Syntax: Type?, nil, if let, !, ??
  • Official Tip:

In Objective-C, nil is a pointer to a nonexistent object. In Swift, nil isn’t a pointer—it’s the absence of a value of a certain type. Optionals of any type can be set to nil, not just object types.

2. Example Analysis
#

Documentation Source Code:

var serverResponseCode: Int? = 404
serverResponseCode = nil // Now contains no value

// Optional Binding (Safe Unwrapping)
if let actualNumber = Int("123") {
    print("Has value: \(actualNumber)")
} else {
    print("Conversion failed")
}

// Implicitly Unwrapped Optionals
let assumedString: String! = "Always has value"
let implicitString: String = assumedString // Automatically unwrapped, no ! required

Logic Explanation:

  • Int("123") returns an Int? because the conversion might fail.
  • if let is a syntax unique to Swift used to check if an Optional has a value. If it does, it unwraps it and assigns it to a temporary constant actualNumber, which is a safe, non-Optional type within the if block.
  • String! is used for cases where a value is “guaranteed to exist after declaration” (such as UI components after initialization). You don’t need to unwrap it every time you use it, but if it is nil, it will cause a Crash.

3. C# Developer’s Perspective
#

Concept Mapping:

  • Similar to C#’s Nullable<T> (int?) and C# 8.0+ Nullable Reference Types (string?).
  • However, Swift’s Optional is implemented as an Enum (Case None, Case Some(Wrapped)).

C# Comparison Code:

// C#
int? serverResponseCode = 404;
serverResponseCode = null;

// C# Pattern Matching (Similar to Swift's if let)
string possibleNumber = "123";
if (int.TryParse(possibleNumber, out int actualNumber)) {
     Console.WriteLine($"Has value: {actualNumber}");
}
// Or for Nullable Types
if (serverResponseCode is int code) {
    // code is int here, not int?
}

// C# has no direct equivalent to Implicitly Unwrapped Optionals (!)
// Usually use string! to tell the compiler "I know this isn't null" (Null-forgiving operator)

Key Difference Analysis:

  • Syntax: Swift’s if let x = opt syntax is very concise. C# requires using is pattern matching if (opt is {} x) to achieve a similar effect.
  • Behavior:
    • C# Reference Types may still be null by default (unless nullable context is enabled and warnings are ignored), and the Runtime may still throw a NullReferenceException.
    • Swift enforces checks through the compiler. If you don’t unwrap an Optional, you cannot use the value inside it, fundamentally eliminating accidental Null Reference errors.
  • Force Unwrapping: Swift’s ! (Force Unwrap) will Crash directly if it encounters nil; this is similar to accessing .Value in C# but throwing an exception when null.

Error Handling
#

1. Core Concepts
#

  • Concept Explanation: Swift uses throws, try, and do-catch to handle predictable errors. This is different from Optionals; Optionals handle the “absence of a value,” while Error Handling handles the “reason for operational failure.”
  • Key Syntax: throws, try, do { ... } catch { ... }

2. Example Analysis
#

Documentation Source Code:

func makeASandwich() throws {
    // ... might throw an error
}

do {
    try makeASandwich()
    eatASandwich()
} catch SandwichError.outOfCleanDishes {
    washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

Logic Explanation: A function declared with throws indicates it might fail. When calling it, you must precede it with try and wrap it in a do-catch block to handle exceptions. Swift errors are usually Enums and can carry associated values (like the list carried by missingIngredients).

3. C# Developer’s Perspective
#

Concept Mapping: Corresponds to C#’s try-catch mechanism.

C# Comparison Code:

// C#
void MakeASandwich() {
    // ... throw new Exception(...)
}

try {
    MakeASandwich();
    EatASandwich();
} catch (OutOfCleanDishesException) {
    WashDishes();
} catch (MissingIngredientsException ex) {
    BuyGroceries(ex.Ingredients);
}

Key Difference Analysis:

  • Syntax: Swift requires try to be explicitly written at the call site, allowing developers to see at a glance which line of code might throw an error. C# throws implicitly.
  • Behavior: C# Exceptions are expensive objects (including Stack Trace). Swift Errors are usually lightweight Structs or Enums with very low performance overhead; therefore, using Errors for flow control is acceptable in Swift.

Assertions and Preconditions
#

1. Core Concepts
#

  • Concept Explanation: Used to check logical assumptions during runtime. If the condition is false, the program terminates. This is an important tool for debugging and ensuring correct program state.
  • Key Syntax: assert (Effective only in Debug mode), precondition (Effective in both Debug and Release)

2. Example Analysis
#

Documentation Source Code:

let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
// Will trigger a crash in Debug mode

// Index check
precondition(index > 0, "Index must be greater than zero.")

3. C# Developer’s Perspective
#

Concept Mapping:

  • Swift assert corresponds to C# Debug.Assert.
  • Swift precondition corresponds to C# Trace.Assert or manual if (!cond) throw ....

C# Comparison Code:

// C#
using System.Diagnostics;

int age = -3;
Debug.Assert(age >= 0, "A person's age can't be less than zero.");

Key Difference Analysis:

  • Swift clearly distinguishes between “Debug only (assert)” and “Check in production as well (precondition)” through syntax, whereas C# typically relies on the System.Diagnostics namespace or Contracts to handle this.