28/02/2024
Introduktion til Rust på iOS
I en verden, hvor ydeevne og sikkerhed er altafgørende, dukker programmeringssproget Rust op som en stærk konkurrent. Mange udviklere, der er vant til sprog som Swift, undrer sig over, om det er muligt at udnytte Rusts styrker direkte i deres iOS-applikationer. Svaret er et rungende ja! Denne artikel guider dig trin for trin gennem processen med at oprette et simpelt Rust-bibliotek og integrere det problemfrit i et SwiftUI-projekt. Vi vil demonstrere, hvordan du kan kombinere det bedste fra begge verdener: Rusts robusthed og Swifts brugervenlighed.

Oprettelse af et Rust-bibliotek til iOS
Før vi kan integrere Rust i vores iOS-projekt, skal vi først oprette selve Rust-biblioteket. Lad os starte med at definere, hvad vores bibliotek skal kunne. For dette eksempel vil vi skabe en simpel tæller. Denne tæller vil kunne initialiseres, inkrementeres og derefter frigives sikkert. Dette er et grundlæggende, men illustrativt eksempel på, hvordan man eksponerer Rust-funktionalitet.
Trin 1: Initialiser et nyt Rust-projekt
Åbn din terminal og naviger til din projektmappe. Opret et nyt Rust-bibliotek ved hjælp af Cargo, Rusts pakkehåndtering og build-system:
cd ~/personlig/BlogPostExample cargo new --lib example-rustTrin 2: Konfigurer Cargo til statisk bibliotek
For at kunne importere vores Rust-kode i Xcode, skal vi konfigurere Cargo til at bygge et statisk bibliotek. Rediger din Cargo.toml-fil og tilføj følgende sektion:
[package] name = "example-rust" version = "0.1.0" edition = "2021" [lib] crate-type = ["staticlib"] [dependencies] Trin 3: Implementering af Tæller-funktionalitet
Nu skal vi definere vores Counter-struktur og dens metoder i src/lib.rs. Vi vil implementere en simpel Fibonacci-sekvens for at gøre det lidt mere interessant end en simpel tæller:
pub struct Counter { v1: u64, v2: u64, } impl Default for Counter { fn default() -> Self { Self { v1: 1, v2: 2, } } } impl Counter { pub fn increment(&mut self) -> u64 { let r = self.v1; self.v1 = self.v2; self.v2 = self.v1 + r; r } } Trin 4: Oprettelse af et C API
Swift kan ikke direkte kalde Rust-funktioner. Derfor skal vi eksponere vores Rust-kode via et C API. Dette gøres ved hjælp af `extern "C"` og `#[no_mangle]` attributterne. Disse sikrer, at funktionerne kompileres med C-navngivningskonventioner, så de kan kaldes fra andre sprog.
pub mod c_api { use super::Counter; #[no_mangle] pub extern "C" fn counter_make() -> *mut std::ffi::c_void { let b = Box::new(Counter::default()); Box::into_raw(b).cast() } #[no_mangle] pub extern "C" fn counter_next(ptr: *mut std::ffi::c_void) -> u64 { let mut b = unsafe { Box::from_raw(ptr.cast::()) }; let r = b.increment(); std::mem::forget(b); r } #[no_mangle] pub extern "C" fn counter_free(ptr: *mut std::ffi::c_void) { let b = unsafe { Box::from_raw(ptr.cast::()) }; drop(b) } } Denne C API giver os funktionerne counter_make til at oprette en ny tæller, counter_next til at inkrementere den og counter_free til at frigive hukommelsen sikkert.
Trin 5: Oprettelse af en Header-fil
For at Swift kan kende til vores C API, skal vi oprette en header-fil (.h). Opret en fil ved navn example-rust.h i roden af dit Rust-projekt:
#ifndef EXAMPLE_RUST_H #define EXAMPLE_RUST_H #include void* counter_make(); uint64_t counter_next(void*); void counter_free(void*); #endif Test af C API (Valgfrit, men anbefalet)
Før vi dykker ned i Xcode, er det en god idé at teste vores C API for at sikre, at den fungerer som forventet. Opret en simpel C-fil (f.eks. test.c) i roden af dit projekt:
#include <stdio.h> #include "example-rust/example-rust.h" int main() { void* ptr = counter_make(); for (int i = 0; i < 5; i++) { uint64_t next = counter_next(ptr); printf("%llu\n", next); } counter_free(ptr); return 0; } Kompiler og kør derefter C-programmet. Først skal du bygge Rust-biblioteket i debug-mode:
cargo build Kompiler derefter C-programmet og link det med Rust-biblioteket:
cc -Lexample-rust/target/debug -lexample_rust test.c -o test ./test Hvis alt er gået korrekt, bør du se outputtet 1 2 3 5 8, hvilket bekræfter, at dit Rust-bibliotek fungerer som forventet.
Kompilering af Rust til iOS
Nu kommer vi til den del, hvor vi skal tilpasse vores Rust-kompilering til iOS-arkitekturerne. Dette involverer brug af rustup til at tilføje de nødvendige mål-arkitekturer.
Trin 1: Tilføj iOS-målarkitekturer
Tilføj arkitekturerne for både den fysiske enhed (aarch64-apple-ios) og simulatoren (aarch64-apple-ios-sim). Hvis du bruger en Intel Mac, skal du muligvis bruge x86_64-apple-ios-sim for simulatoren.
rustup target add aarch64-apple-ios aarch64-apple-ios-sim Trin 2: Byg Rust-biblioteket til iOS
Byg nu dit Rust-bibliotek ved hjælp af de tilføjede målarkitekturer:
cargo build --target aarch64-apple-ios cargo build --target aarch64-apple-ios-sim Disse kommandoer vil generere de statiske biblioteker (.a-filer) i de respektive target-mapper, klar til integration i Xcode.
Integration i Xcode
Nu er det tid til at bringe vores Rust-bibliotek ind i vores SwiftUI iOS-applikation. Dette involverer at konfigurere Xcode til at finde og linke med vores kompilerede Rust-kode.
Trin 1: Opret et nyt Xcode-projekt
Hvis du ikke allerede har gjort det, skal du oprette et nyt Xcode-projekt. Vælg "iOS App" og derefter "SwiftUI" som brugergrænseflade. Giv det et passende navn, f.eks. "BlogPostExample". Tilføj en simpel knap og tekstfelt til din ContentView.swift.
Trin 2: Tilføj Rust-biblioteket som et Framework
I Xcode skal du navigere til projektets indstillinger. Klik på det øverste projektnavn i filoversigten. Under fanen "General", find sektionen "Frameworks, Libraries, and Embedded Content". Klik på "+"-knappen, vælg "Add Other...", derefter "Add Files...", og find din kompilerede Rust-biblioteksfil: example-rust/target/aarch64-apple-ios/debug/libexample_rust.a.
Trin 3: Konfigurer Library Search Paths
For at Xcode kan finde biblioteket, skal vi angive søgestier. Gå til fanen "Build Settings" i projektets indstillinger. Søg efter "Search Paths" og find "Library Search Paths". Tilføj følgende stier:
$(SRCROOT)/example-rust/target/aarch64-apple-ios/debug(for enheder)$(SRCROOT)/example-rust/target/aarch64-apple-ios-sim/debug(for simulatorer)
Sørg for, at disse stier er sat til "Debug" og "Any iOS SDK" / "Any iOS Simulator SDK" efter behov.

Integration i Swift
Det sidste trin er at få Swift til at kommunikere med vores Rust-bibliotek via det C API, vi har oprettet.
Trin 1: Opret en Bridging Header
Swift har brug for en måde at importere C-kode på. Dette gøres typisk via en bridging header. Hvis du tilføjer en ny fil til dit Xcode-projekt og vælger "Objective-C File", vil Xcode automatisk foreslå at oprette en bridging header. Du kan derefter slette Objective-C-filen og beholde selve header-filen (f.eks. {DitProjektNavn}-Bridging-Header.h).
I din bridging header skal du inkludere din Rust-header-fil:
#ifndef EXAMPLE_RUST_BRIDGE_H #define EXAMPLE_RUST_BRIDGE_H #include "../example-rust/example-rust.h" #endif Sørg for, at "Objective-C Bridging Header"-indstillingen i "Build Settings" peger korrekt på denne fil.
Trin 2: Kald Rust-funktioner fra Swift
Nu kan vi endelig kalde vores Rust-funktioner fra SwiftUI. Da counter_make returnerer en rå pointer, skal vi erklære den som UnsafeMutableRawPointer? i Swift. Derefter kan vi kalde counter_next med denne pointer.
import SwiftUI struct ContentView: View { @State var count = UInt64(0) @State var counter: UnsafeMutableRawPointer? = nil var body: some View { VStack { Text("Count is \(count)") Button("Increment") { if counter == nil { counter = counter_make() } count = counter_next(counter!) } .padding() } .padding() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } Med disse ændringer kan du nu bygge og køre din app. Hver gang du trykker på knappen, vil den kalde din Rust-kode, inkrementere tælleren og vise det nye tal på skærmen.
Fordele ved at bruge Rust på iOS
Integrationen af Rust i iOS-projekter tilbyder flere markante fordele:
- Ydeevne: Rust er kendt for sin rå hastighed, der ofte kan konkurrere med C og C++. Dette er især nyttigt til CPU-intensive opgaver som billedbehandling, kryptografi eller komplekse beregninger.
- Hukommelsessikkerhed: Rusts unikke ejerskabssystem (ownership system) og borrow checker forhindrer almindelige hukommelsesfejl som null-pointer dereferencing og data races ved kompileringstidspunktet. Dette reducerer markant antallet af runtime-fejl og sikkerhedshuller.
- Concurrency: Rust gør det nemmere og sikrere at skrive parallel kode, hvilket er afgørende for moderne multi-core processorer.
- Cross-Platform Kompatibilitet: Selvom vi fokuserer på iOS, er Rust et cross-platform sprog. Den samme Rust-kode kan potentielt genbruges på andre platforme som Android, WebAssembly eller desktop.
Udfordringer og Overvejelser
Selvom det er muligt og fordelagtigt at bruge Rust på iOS, er der også nogle udfordringer, man skal være opmærksom på:
- Kompleksitet: Opsætningen kan være mere kompleks end at arbejde udelukkende med Swift, især med hensyn til build-processen og håndtering af C API'er.
- Debugging: Debugging af kode, der spænder over både Swift og Rust, kan være mere udfordrende. Værktøjer som LLDB kan hjælpe, men det kræver en god forståelse af begge sprog og deres interaktion.
- Kompileringstid: Kompilering af Rust-kode til flere arkitekturer kan øge den samlede build-tid.
- Afhængigheder: Håndtering af Rust-afhængigheder (crates) i et Xcode-projekt kræver omhyggelig konfiguration.
Ofte Stillede Spørgsmål (FAQ)
Kan jeg bruge Rust-biblioteker direkte i mit iOS-projekt?
Ja, ved at kompilere Rust-biblioteket som et statisk bibliotek og eksponere en C API, kan du integrere det i dit Xcode-projekt. Swift kan derefter kalde disse C-funktioner.
Hvad er den bedste måde at håndtere hukommelse på, når man kalder Rust fra Swift?
Brug C API'en til at allokere og frigive hukommelse. Sørg for, at hukommelse, der allokeres i Rust (f.eks. via Box::into_raw), også frigives korrekt i Rust (via Box::from_raw og drop) ved hjælp af en dedikeret C-funktion som counter_free.
Hvordan kan jeg debugge min Rust-kode, der kører på iOS?
Du kan bruge Xcode's debugger (LLDB) til at sætte breakpoints i din Swift-kode, der kalder Rust-funktionerne. Når Rust-koden kaldes, kan du inspicere hukommelse og variable. For mere dybdegående Rust-debugging kan det være nødvendigt at bruge Rusts egne debugging-værktøjer og muligvis køre dele af din kode uden for Xcode for at isolere problemer.
Er det muligt at bygge Rust-binaries for iOS på Linux?
Selvom det er mere komplekst, er det muligt at bygge Rust-binaries for iOS på Linux. Dette kræver opsætning af et cross-compilation miljø ved hjælp af cross-compilers og korrekt konfigurering af Rusts target-specifikationer. Det er typisk en mere involveret proces end at gøre det på en macOS-maskine.
Konklusion
At integrere Rust i dine iOS-apps åbner op for en verden af muligheder for at forbedre ydeevne og sikkerhed. Selvom der er en indledende læringskurve og opsætningskompleksitet, gør Rusts styrker det til et attraktivt valg for udviklere, der ønsker at skubbe grænserne for, hvad der er muligt på iOS-platformen. Ved at følge denne guide kan du tage dine første skridt mod at udnytte Rusts kraft i dine egne projekter.
Hvis du vil læse andre artikler, der ligner Brug Rust i dine iPhone-apps, kan du besøge kategorien Teknologi.
