Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Sharing Code between iOS and Android with Rust

Sharing Code between iOS and Android with Rust

When having to share code between iOS and Android, most companies choose C++. It is a well known language with very good tooling, but it also has a lot of pitfalls. For one, it is a very complex language. It also makes it really easy to accidentally introduce memory leaks or segmentation faults; especially if you're used to automatic memory management via a GC (Kotlin) or Arc (Swift). It also looks quite different from modern language like Swift or Kotlin. Now that we iOS developers got (mostly) rid of Objective-C, and Android Developers got (mostly) rid of Java, it feels archaic having to go back to a language with an archaic Syntax like C++.
Rust looks and feels a lot like Kotlin or Swift, and it offers the same easy ways of sharing code as C++. In addition to that, Rust has a very safe memory management model, high performance, a way to do fearless concurrency, and a very rich package ecosystem. As a bonus, it compiles to WebAssembly, so the shared code could also be used in any HTML5 app. This talk showcases how one can share very performant cross platform code between iOS, Android and others by using Rust.

Benedikt Terhechte

February 08, 2019
Tweet

More Decks by Benedikt Terhechte

Other Decks in Programming

Transcript

  1. @terhechte

    View Slide

  2. @terhechte
    ABOUT ME
    Benedikt Terhechte
    @terhechte
    www.appventure.me
    iOS / macOS / Linux Developer

    View Slide

  3. @terhechte
    • German Social
    Network
    • Based in Hamburg
    • 15 Mio Users
    • Native Apps on all
    platforms

    View Slide

  4. @terhechte

    View Slide

  5. @terhechte
    3 NATIVE PLATFORMS
    • Implement everything three times
    • Amount of bugs X 3
    • Alignment Overhead
    iOS, ANDROID, WINDOWS

    View Slide

  6. @terhechte
    FIND WAYS OF SHARING CODE
    • React Native
    • C#
    • Flutter
    • Model Layer in C++

    View Slide

  7. @terhechte
    USE NATIVE LANGUAGE FOR UI*
    • Kotlin (or Java) for Android
    • Swift (or Objective-C) for iOS
    • C# (or C++) for Windows
    * For Complex Projects

    View Slide

  8. @terhechte
    USE C++ FOR SHARED MODEL
    • Facebook
    • Spotify
    • SoundCloud
    • PSPDFKit
    • …

    View Slide

  9. @terhechte
    WHY NOT
    C++?

    View Slide

  10. @terhechte
    IMAGES.GOOGLE.COM
    C++ MEME

    View Slide

  11. @terhechte

    View Slide

  12. @terhechte

    View Slide

  13. @terhechte

    View Slide

  14. @terhechte

    View Slide

  15. @terhechte

    View Slide

  16. @terhechte

    View Slide

  17. @terhechte
    C++ IS NOT BAD
    • Very complex language
    • Tricky finding good developers
    • Easy to introduce serious bugs

    View Slide

  18. @terhechte
    WHY NOT
    C++?

    View Slide

  19. @terhechte
    Why not
    Kotlin Native?

    View Slide

  20. @terhechte
    WHAT DOES C++ OFFER?
    • Build for cross Platform Development
    • Very High Speed
    • Huge Standard Library

    View Slide

  21. @terhechte
    • Still Young
    • Many Performance
    Optimizations yet to come
    • Many Packages are still JVM
    only
    • SDKs (i.e. Dropbox, Firebase)
    often don’t exist for Kotlin
    Native yet
    KOTLIN NATIVE

    View Slide

  22. @terhechte
    WHAT ABOUT RUST?

    View Slide

  23. @terhechte
    QUICK PERFORMANCE
    COMPARISON

    View Slide

  24. @terhechte
    A SUM OF STRINGS < 100
    “54,28,42,77,34 ,90 , 8 ,
    120,76 , 52, 117 ,21 ,5”
    SPLIT, TRIM, TO INT, FILTER, SUM

    View Slide

  25. @terhechte
    fun sumSplit(contents: String): Int {
    return contents.split(",").asSequence()
    .map({ str -> str.trim().toIntOrNull() })
    .filterNotNull()
    .filter({ number -> number < 100 })
    .fold(0, { a, b -> a + b})
    }
    KOTLIN

    View Slide

  26. @terhechte
    fn sum_and_split(contents: String) -> i32 {
    contents.split(",")
    .map(|s| { s.trim() })
    .filter_map(|s| { s.parse().ok() })
    .filter(|number: &i32| { *number < 100 })
    .fold(0, |acc, x| acc + x)
    }
    RUST

    View Slide

  27. @terhechte
    // Lifted from StackOverflow
    std::vector split(const std::string &s, char delim) {
    std::stringstream ss(s);
    std::string item;
    std::vector elems;
    while (std::getline(ss, item, delim)) {
    elems.push_back(std::move(item));
    }
    return elems;
    }
    // Lifted from StackOverflow
    inline std::string trim(const std::string &s) {
    auto wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
    return std::string(wsfront,std::find_if_not(s.rbegin(),std::string::const_reverse_iterator(wsfront),[](int c){return std::isspace(c);}).base());
    }
    inline std::optional operate(const std::string &s) {
    auto trimmed = trim(s);
    try {
    auto number = std::stoi(trimmed);
    return number;
    }
    catch (int ) {
    return {};
    }
    }
    int test(std::string str) {
    // I found it tricky to use a fully functional C++ version without external libraries such as
    // boost.
    // So, instead, enjoy this half-hearted attempt
    auto contents = split(str, ',');
    std::vector> converted;
    converted.resize(contents.size());
    std::transform(contents.begin(), contents.end(), converted.begin(), operate);
    auto sum = 0;
    for (std::optional n: converted) {
    if (n.has_value() ) {
    if (n.value() < 100) {
    sum += n.value();
    }
    }
    }
    return sum;
    }
    C++

    View Slide

  28. @terhechte
    0s
    3,5s
    7s
    10,5s
    14s
    Rust C++ Kotlin
    12,52s
    0,6s
    0,31s
    Compile Strings Functional

    View Slide

  29. @terhechte
    0s
    0,5s
    1s
    1,5s
    2s
    Rust C++ Kotlin
    1,82s
    0,1s
    0,07s
    Run Strings Functional

    View Slide

  30. @terhechte
    RUST IS CRAZY FAST
    (C++ IS FASTER)

    View Slide

  31. @terhechte

    View Slide

  32. @terhechte
    WHAT IS RUST
    • Started 2006
    • Strongly Typed, Compiled
    • LLVM Based
    • Driven by Mozilla

    View Slide

  33. @terhechte
    Mascot: Ferris

    View Slide

  34. @terhechte
    –Wikipedia
    “Rust is intended to be a language for highly
    concurrent and highly safe systems.”

    View Slide

  35. @terhechte
    SOME PROJECTS USING IT
    • Firefox (Rendering, CSS)
    • Dropbox (Custom Server Filesystem)
    • Oracle (Container Runtime)
    • Yelp, CloudFlare, …

    View Slide

  36. @terhechte
    CROSS PLATFORM
    • Is not at home on any platform
    • Firefox is using it. Firefox is per definition cross
    platform
    • Rust & Packages run equally well on Windows, Linux,
    Android, iOS, macOS, etc

    View Slide

  37. @terhechte
    ARCHITECTURES
    x86, x86_64, i386, ARM, ARM64, WASM, MIPS, MIPS64, SPARC, RISC, 16bit
    MSP430, etc
    OPERATING SYSTEMS
    Windows, macOS, Linux, iOS, Android, FreeBSD, NetBSD, Haiku, Redox, Haiku,
    etc
    TARGETS
    WebAssembly, Ruby, Python, JVM, C, C++, more

    View Slide

  38. @terhechte

    View Slide

  39. @terhechte
    let vector = vec![1, 2, 3, 4];
    for element in vector {
    println!("The number is: {}", element);
    }
    vector.iter()
    .for_each(|chr| println!("A Char: {}", chr) );
    ARRAYS [Vectors]

    View Slide

  40. @terhechte
    let mut vector: Vec = Vec::new();
    vector.push(5);
    MUTABILITY

    View Slide

  41. @terhechte
    let mut string = "hey! 檀れい";
    string.make_ascii_uppercase();
    assert!(string == "HEY! 檀れい");
    MUTABILITY

    View Slide

  42. @terhechte
    fn add_two(first: i32, second: i32) -> i32 {
    return first + second;
    }
    FUNCTIONS

    View Slide

  43. @terhechte
    struct User {
    name: String,
    age: i32
    }
    impl User {
    fn formatted(&self) -> String {
    return format!(“{} {}", self.name, self.age);
    }
    }
    STRUCT METHODS

    View Slide

  44. @terhechte
    enum RGB {
    Red, Green, Blue
    }
    enum Expression {
    Const(i32),
    Sum(i32, i32),
    Text(String)
    }
    SEALED CLASSES / ENUMS [Enums]

    View Slide

  45. @terhechte
    struct Container {
    value: T
    }
    let contained = Container { value: 15 }
    GENERICS

    View Slide

  46. @terhechte
    fn add_anything>(a: N, b: N)
    -> N {
    return a + b;
    }
    GENERIC CONSTRAINTS

    View Slide

  47. @terhechte
    trait Countable {
    fn counted() -> i32;
    }
    INTERFACES

    View Slide

  48. @terhechte
    trait Countable {
    fn counted() -> i32;
    }
    struct Number { number: i32 }
    impl Countable for Number {
    fn counted(&self) -> i32 {
    return self.number;
    }
    }
    INTERFACES

    View Slide

  49. @terhechte
    • No Classes (But Reference Types and Interface
    Inheritance)
    • Limited Reflection (But Hygienic Macros)
    • No Function Overloading (But Operator Overloading)

    View Slide

  50. @terhechte
    !
    BENCHMARKS ARE THE DEVIL!
    Code Examples &
    Benchmarks

    View Slide

  51. @terhechte
    JUST CURSORY BENCHMARKS
    • Mac mini 2018, 3.2GHz
    • Idiomatic code
    • Will release the benchmark suite on Twitter

    View Slide

  52. @terhechte
    VERSIONS
    • Kotlin Native 1.0.3 (6. Dec 2018)
    • Rust 1.32.0 (16. Jan 2019)
    • C++ Clang 6.0

    View Slide

  53. @terhechte
    KOTLIN: FUN
    RUST: FN

    View Slide

  54. @terhechte
    FINDING PRIME NUMBERS
    And awful solution using functional programming

    View Slide

  55. @terhechte
    fun isPrime(number: Int): Boolean {
    return (num > 1) && !((2 until num)
    .any({ n -> num % n == 0 }))
    }
    fn is_prime(num: i32) -> bool {
    num > 1 && !(2..num)
    .any(|n| num % n == 0)
    }
    PRIME NUMBERS

    View Slide

  56. @terhechte
    0s
    3s
    6s
    9s
    12s
    Rust C++ Kotlin
    11,26s
    0,35s
    0,53s
    Compile Primes

    View Slide

  57. @terhechte
    0s
    25s
    50s
    75s
    100s
    Rust C++ Kotlin
    90,39s
    0,85s
    0,85s
    Run Primes

    View Slide

  58. @terhechte
    CHUNKS OF NUMBERS

    View Slide

  59. @terhechte
    fun resize_chunk(chunk: List, scale: Int):
    List {
    return chunk.chunked(scale)
    .map({ innerChunk -> innerChunk.sum() })
    }
    fun resize_image(image: List, width: Int, scale: Int):
    List {
    return image.chunked(width)
    .map({ innerChunk ->
    resize_chunk(innerChunk, scale) })
    .flatten()
    }
    CHUNKS
    flatten(chunked > map > resize_chunk > chunked > map > sum)

    View Slide

  60. @terhechte
    fn resize_chunk<'a>(chunk: &'a [usize], scale: usize)
    -> impl Iterator + 'a {
    chunk.chunks(scale)
    .map(|chunk| { chunk.iter().sum::() })
    }
    fn resize_image(image: &[usize], width: usize, scale: usize)
    -> Vec {
    image.chunks(width)
    .map(|slice| resize_chunk(slice, scale))
    .flatten()
    .collect()
    }
    CHUNKS
    flatten(chunks > map > resize_chunk > chunk > map > sum)

    View Slide

  61. @terhechte
    uint64_t* resize_image(uint64_t* image, uint64_t size, uint64_t width, uint64_t scale, uint64_t* rsize) {
    uint64_t *result = malloc(sizeof(uint64_t) * (size / scale));
    uint64_t pos = 0;
    for (uint64_t i=0; ifor (uint64_t i2=i; i2<(i + width); i2+=scale) {
    uint64_t sum = 0;
    for (uint64_t i3=i2; i3<(i2 + scale); i3+=1) {
    sum += image[i3];
    }
    result[pos++] = sum;
    }
    }
    *rsize = pos;
    return result;
    }
    CHUNKS ©

    View Slide

  62. @terhechte
    0s
    3,5s
    7s
    10,5s
    14s
    Rust C Kotlin
    13,92s
    0,12s
    0,67s
    Compile Chunks Imperative

    View Slide

  63. @terhechte
    0s
    40s
    80s
    120s
    160s
    Rust C Kotlin
    143,76s
    0,67s
    0,76s
    Run Chunks Imperative

    View Slide

  64. @terhechte
    fun resize_image(image: IntArray, width: Int, scale: Int): IntArray {
    val result = IntArray(image.size / scale)
    var pos = 0
    for (i in 0 until image.size step width) {
    for (i2 in i until (i + width) step scale) {
    var sum = 0
    for (i3 in i2 until (i2 + scale)) {
    sum += image[i3]
    }
    result[pos] = sum
    pos += 1
    }
    }
    return result
    }
    CHUNKS

    View Slide

  65. @terhechte
    0s
    1,5s
    3s
    4,5s
    6s
    Rust C Kotlin
    5,76s
    0,67s
    0,76s
    Run Chunks Imperative

    View Slide

  66. @terhechte
    JSON
    Parse JSON into an array of
    structs

    View Slide

  67. @terhechte
    #[derive(Deserialize)]
    struct Media {
    author: User,
    likes: Vec,
    comments: Vec,
    images: HashMap,
    description: String
    }
    let media: Vec =
    serde_json::from_str(contents)?;
    RUST

    View Slide

  68. @terhechte
    @Serializable
    data class Media(
    val author: User,
    val likes: Array,
    val comments: Array,
    val images: Map,
    val description: String)
    val obj =
    Json.parse(Media.serializer().list, contents)
    KOTLIN

    View Slide

  69. @terhechte
    0s
    4s
    8s
    12s
    16s
    Rust Kotlin
    15,82s
    0,17s
    27MB JSON into Structs

    View Slide

  70. @terhechte
    0MB
    100MB
    200MB
    300MB
    400MB
    Rust Kotlin
    331MB
    6MB
    Average Memory

    View Slide

  71. @terhechte
    • Very similar Code
    • Much faster
    • Less Memory

    View Slide

  72. @terhechte
    DEMO

    View Slide

  73. @terhechte
    WHAT?
    • Focus on Model Layer
    • Kotlin is much better than Rust at UI Code
    • Swift is much better than Rust at UI Code

    View Slide

  74. @terhechte
    HOW?
    • For iOS: Compile Rust into C library. Use via Objective-C
    Bridge.
    • For Android: Compile Rust via Swig

    View Slide

  75. @terhechte
    DEMO

    View Slide

  76. @terhechte

    View Slide

  77. @terhechte
    CONCURRENCY
    FEARLESS*
    CONCURRENCY
    * Honestly, that’s what the Rust people call it

    View Slide

  78. @terhechte
    WHY?
    • Multicore devices are ever more common
    • Especially in the model, you want to use all the cores as
    best as possible
    • Concurrency bugs (Data Races) are really easy most
    languages

    View Slide

  79. @terhechte
    let mut data = vec![1, 2, 3];
    for i in 0..100 {
    thread::spawn(|| {
    data.push(i);
    });
    }
    RUST: to force the closure to take ownership of `data`
    use the `move` keyword

    View Slide

  80. @terhechte
    let mut data = vec![1, 2, 3];
    for i in 0..100 {
    thread::spawn(move || {
    data.push(i);
    });
    }
    RUST: capture of moved value `data`

    View Slide

  81. @terhechte
    let mut data = Arc::new(vec![1, 2, 3]);
    for i in 0..100 {
    let data_ref = data.clone();
    thread::spawn(move || {
    data_ref.push(i);
    });
    }
    RUST: cannot borrow data_ref as mutable.
    Arc is immutable

    View Slide

  82. @terhechte
    let mut data = Arc::new(Mutex::new(vec![1, 2]));
    for i in 0..100 {
    let data_ref = data.clone();
    thread::spawn(move || {
    let mut data = data_ref.lock.unwrap();
    data_ref.push(i);
    });
    }
    RUST: I Compile

    View Slide

  83. @terhechte
    use rayon::prelude::*;
    fn sum_of_squares(input: &Vec) -> i32 {
    input.par_iter() // <-- just change that!
    .map(|&i| i * i)
    .sum()
    }

    View Slide

  84. @terhechte
    • Rust is really powerful at multicore programming
    • It also becomes much easier to write multicore code
    • Ecosystem is constantly improving

    View Slide

  85. @terhechte

    View Slide

  86. @terhechte
    OTHER THINGS
    • Hygienic Macros with IDE support
    • Go-Like Channels in the standard library
    • Futures (async await) coming soon
    • SIMD Support
    • Limited support for using C++ libraries

    View Slide

  87. @terhechte
    OTHER THINGS
    • Really fast web frameworks
    • Fantastic package manager
    • Very welcoming community

    View Slide

  88. @terhechte
    UPSIDES
    • Easier & Safer than C++
    • Much more similar to Kotlin than to C++
    • Really Fast
    • Generated binaries are kinda small (starting from
    ~500kb)

    View Slide

  89. @terhechte
    UPSIDES
    • Consumes very little memory
    • Excels at concurrency
    • Great, cross-platform ecosystem
    • Also share code with Ruby, Python, Backend etc

    View Slide

  90. @terhechte
    DOWNSIDES
    • No classes, no easy shared mutable state
    • Initial learning phase is hard (Memory Management)
    • Difficult to hire but much easier to learn than C++
    • Tooling for Android / iOS is especially young & verbose

    View Slide

  91. @terhechte
    WHAT NOW?

    View Slide

  92. @terhechte
    IF YOU’RE JUST PARSING JSON
    FROM THE SERVER, FOR THE LOVE
    OF GOD, DON’T USE RUST.

    View Slide

  93. @terhechte
    WHERE IS C++ NEEDED
    • Complex projects with lots of on-device logic
    • PDF Parsing (PSPDFKit)
    • Cross Platform DRM Music Player (Spotify)
    • Office Suite, Image Editing, etc

    View Slide

  94. @terhechte
    IF YOU NEED SPEED, CONCURRENCY,
    LOW MEMORY, & CROSS PLATFORM
    ABSTRACTIONS, THEN
    CONSIDER RUST INSTEAD OF C++

    View Slide

  95. @terhechte
    POLITICS
    THE IOS GUYS WANT SWIFT
    THE ANDROID GUYS WANT KOTLIN
    RUST IS THE SAFE MIDDLE ;)

    View Slide

  96. @terhechte
    Benedikt Terhechte - twitter.com/terhechte
    Podcast: contravariance.rocks
    Questions? I have 100 more slides
    XING is HIRING IN GERMANY, SPAIN, AND PORTUGAL

    View Slide