[{"data":1,"prerenderedAt":2214},["ShallowReactive",2],{"blog-\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-1\u002F":3,"related-blog-\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-1\u002F":2178},{"id":4,"title":5,"abstract":6,"author":6,"body":7,"description":2160,"excerpt":6,"extension":2161,"head":6,"image":6,"keywords":2162,"meta":2168,"modified":6,"navigation":251,"path":2170,"proficiencyLevel":2171,"published":2172,"rawbody":2173,"schemaOrg":6,"schemaType":2174,"seo":2175,"stem":2176,"__hash__":2177},"blog\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-1.md","Ferrostar: Building a Cross-Platform Navigation SDK in Rust (Part 1)",null,{"type":8,"value":9,"toc":2142},"minimark",[10,14,18,21,26,39,42,53,57,60,63,74,77,80,113,117,120,131,134,137,150,157,161,168,181,186,189,206,216,223,318,333,364,375,413,434,464,544,548,551,557,562,569,648,677,680,684,691,706,805,808,815,914,918,921,924,1425,1435,1453,1479,1483,1486,1576,1597,1600,1662,1668,1703,1709,1713,1716,1808,1823,1836,1911,1921,2023,2033,2045,2048,2052,2058,2065,2074,2077,2081,2084,2101,2111,2114,2118,2126,2129,2138],[11,12,5],"h1",{"id":13},"ferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-1",[15,16,17],"p",{},"This is the first in a series of technical blog posts\ncovering the joys and challenges\nof building a cross-platform shared library,\nwith a focus on use in mobile applications.",[15,19,20],{},"As a motivating example to frame things,\nwe'll follow the journey of building Ferrostar,\na free and open-source navigation SDK.\nWe want to offer a modern SDK that is cross-platform,\nvendor-neutral, and easily extensible\nboth by direct contributors and developers using it in their apps.",[22,23,25],"h2",{"id":24},"why","Why?",[15,27,28,29,32,33,38],{},"Before we get into the technical weeds too much,\nit's worth taking a moment to ask ",[30,31,24],"em",{},".\nAt Stadia Maps,\nwe have offered ",[34,35,37],"a",{"href":36},"\u002Fproducts\u002Frouting-navigation\u002F","highly customizable routing","\nsince 2017,\nand support use cases including safe pedestrian routing through lit areas,\ngolf cart routing that prefers multi-use paths,\nand more!\nBut developers are on their own\nwhen it comes to offering a turn-by-turn navigation UI.\nSeveral open-source apps already offer navigation,\nbut the solution is not generalized.\nAmong open-source navigation SDKs,\nMapLibre Navigation Android being the most mature at the time of this writing.\nBut as the name suggests, it is Android-specific.\nAnd since the internals originally came from old open-source Mapbox code,\nit takes considerable effort to use with other vendors,\nand extensibility was never a core design consideration.",[15,40,41],{},"Starting a new navigation SDK is certainly an ambitious project,\nbut at the moment there is no navigation SDK that is\nhigh-quality, cross-platform, and open-source.\nWe're here to change that,\nand enable developers to bring these routing innovations to their users.",[15,43,44,45,52],{},"This post series is about the nuts and bolts of how we plan to pull it off.\nWe'll start with an overview of ways to share common logic across platforms,\nexplain the architecture we settled on,\nand finish with a deep dive into the first technical topic:\nbinding generation with ",[34,46,51],{"href":47,"rel":48,"target":50},"https:\u002F\u002Fmozilla.github.io\u002Funiffi-rs\u002F",[49],"external","_blank","UniFFI",".",[22,54,56],{"id":55},"how-to-share-common-logic","How to Share Common Logic?",[15,58,59],{},"We want to target multiple platforms: iOS and Android for starters.\nCertain code is the same regardless of where you're running it.\nAlgorithms like detecting if the user has strayed off the route\nand calculating the distance to the next turn\nshould be written once and shared across platforms.\nAnd at an even higher level,\nthe broad \"business logic\" of a turn-by-turn navigation experience\nshould be common shared code.",[15,61,62],{},"Broadly speaking, there are two common approaches\nfor sharing code across platforms in mobile apps today.\nFirst, there are cross-platform app development frameworks like\nFlutter, React Native, or Kotlin Multiplatform.\nThey promise the ability to write your business logic and UI once\nand run it anywhere.\nThe second approach is to put your business logic in a shared library.\nYou write your code in a reasonably portable language (usually C++)\nand link this with your (platform-specific) applications.",[15,64,65,66,69,70,73],{},"In general,\nthe cross-platform frameworks are optimized for building ",[30,67,68],{},"applications",",\nnot ",[30,71,72],{},"libraries",".\nWe want to ensure that we can support additional platforms\nwith relative ease.\nYou can't just pick up React Native, for example,\nand run it on a bicycle computer.\nThis is situation is improving though.\nFor example, Flutter now runs on certain embedded devices,\nand Kotlin Multiplatform has a quickly improving support for library targets.\nHowever, these are still very early stage developments,\nand they involve a certain amount of ecosystem lock-in.",[15,75,76],{},"Given our requirements, we opted for the shared library approach.\nHistorically, C++ has been a popular choice for the task.\nIt is portable across a wide range of platforms,\ncan be made to interoperate with almost any other language,\nand usually produces small, well-optimized binaries.\nWe preferred to use something else for a few reasons,\nthe most important being\nthat it is difficult to write safe and correct C++ code,\nespecially as a new contributor.",[15,78,79],{},"At Stadia Maps, we have been shipping Rust in production since 2018.\nOur investment has rewarded us with\nconsistently good performance (Rust is keen on zero-cost abstractions),\nmemory safety,\nand excellent maintainability.\nAnd Rust code tends to have fewer bugs as well!\nRust also has excellent interoperability with other languages\nvia a straightforward FFI using the platform's C ABI,\nin stark contrast to C++ which can be rather complex to interface with.\nRust also has a large and growing list of supported platforms,\nincluding iOS and Android on several architectures.",[15,81,82,83,88,89,94,95,100,101,106,107,112],{},"We aren't alone in our selection of Rust either.\nWe've seen a lot of exciting development in Rust mobile frameworks\nlike ",[34,84,87],{"href":85,"rel":86,"target":50},"https:\u002F\u002Fredbadger.github.io\u002Fcrux\u002F",[49],"Crux",",\n",[34,90,93],{"href":91,"rel":92,"target":50},"https:\u002F\u002Fdioxuslabs.com\u002F",[49],"Dioxus",",\nand ",[34,96,99],{"href":97,"rel":98,"target":50},"https:\u002F\u002Fgithub.com\u002Fcunarist\u002Frinf",[49],"Rinf"," over the past year.\nBut it's not just new experimental projects that are adopting Rust.\nWe know that both ",[34,102,105],{"href":103,"rel":104,"target":50},"https:\u002F\u002Fwiki.mozilla.org\u002FOxidation",[49],"Mozilla","\nand ",[34,108,111],{"href":109,"rel":110,"target":50},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=e2ID05dpJzo",[49],"Lyft","\nare using Rust shared libraries in their mobile apps.\nRust is already being used in production mobile apps today.",[22,114,116],{"id":115},"architecture-overview","Architecture Overview",[15,118,119],{},"Zooming out, let's look at the broader architecture we adopted.\nThe core is the place where we want all shared logic to live.\nIn the context of our navigation SDK,\nthis includes parsing API responses,\nfiguring out when to advance navigation to the next step,\ncalculating the distance to the next turn,\nand so on.",[15,121,122,123,127,128,52],{},"Shared logic in a \"functional core\" isn't new,\nbut we can go one step further and define our data models too!\nThis lets us keep the navigation logic vendor- and platform-agnostic.\nFor example,\nwe define a common location update type with fields like\ncoordinate, timestamp, and heading.\nThis way the core logic doesn't need to care about the differences between\n",[124,125,126],"code",{},"CLLocation"," and ",[124,129,130],{},"android.location.Location",[15,132,133],{},"We're building a Rust library that's callable via the C ABI.\nBut if we want any mobile devs to even try our framework,\nwe'll need to expose a nicer Swift\u002FKotlin interface.\nThese are called bindings,\nand they provide a thin layer that hides the messy details\nof going back and forth between Rust and the \"native\" mobile code.",[15,135,136],{},"Finally, we have the native mobile library.\nThis is the only thing that most app developers will see,\nand it handles communications with the \"outside world\" (ex: internet and GPS),\nand provides a higher level API that can be a bit more \"opinionated.\"\nWe won't say much more about this layer in the rest of our discussions,\nas many volumes have been written on good API and library design.",[15,138,139,143,144,149],{},[140,141,142],"strong",{},"Spoiler to avert anxiety and frantic typing",": the rest of this blog goes deep into\n\"how the sausage is made.\"\nTo save you the tedious work of reproducing the structure for your next library,\nwe've put together a ",[34,145,148],{"href":146,"rel":147,"target":50},"https:\u002F\u002Fgithub.com\u002Fianthetechie\u002Funiffi-starter",[49],"public template repo","\non GitHub which is ready to go with no typing or other fiddling required.\nWith that, let's dive in!",[15,151,152],{},[153,154],"img",{"alt":155,"src":156},"Ferrostar Architecture Diagram","\u002Fimages\u002Fcontent\u002Fferrostar-arch.png",[22,158,160],{"id":159},"bindings-bridging-rust-and-platform-code","Bindings: Bridging Rust and Platform Code",[15,162,163,164,167],{},"If you're familiar with the concept of foreign function interfaces (FFIs),\nyou may remember ",[124,165,166],{},"extern"," declarations,\ncalling convention specifications,\nand having to write a lot of boilerplate.\nBut you know what is really good at generating boilerplate?\nComputers!\nEnter: binding generators.",[15,169,170,171,174,175,180],{},"Mozilla has developed a fantastic tool for binding generation:\n",[34,172,51],{"href":47,"rel":173,"target":50},[49],".\nIn their case, the motivation was to build pieces of Firefox\nwhich could be shared across platforms.\nUniFFI is designed to generate safe bindings that feel idiomatic\nto users of the target language.\nThe ",[34,176,179],{"href":177,"rel":178,"target":50},"https:\u002F\u002Fmozilla.github.io\u002Funiffi-rs\u002Finternals\u002Fdesign_principles.html",[49],"design principles","\nof UniFFI aligned well with our requirements for Ferrostar,\nand we think they tend to be a good fit for most similar mobile use cases.",[182,183,185],"h3",{"id":184},"cargo-workspace-setup","Cargo workspace setup",[15,187,188],{},"Let's dive in with discussing the cargo workspace setup for Ferrostar.\nThis should be a helpful guide for anyone wanting to create their own\ncross-platform library.",[15,190,191,192,127,195,198,199,202,203,205],{},"We chose to structure the Rust portion of our library as a workspace\nwith two member projects: ",[124,193,194],{},"uniffi-bindgen",[124,196,197],{},"ferrostar",".\nAt the time of this writing,\nthe ability to run the binary from the ",[124,200,201],{},"uniffi"," crate\nis only available in nightly,\nso we opted for a second ",[124,204,194],{}," crate in our workspace\nfor the CLI binary.\nHere's what it looks like on disk:",[207,208,213],"pre",{"className":209,"code":211,"language":212},[210],"language-text","├── Cargo.lock\n├── Cargo.toml\n├── ferrostar\n│   ├── Cargo.toml\n│   └── src\n│       └── lib.rs\n└── uniffi-bindgen\n    ├── Cargo.toml\n    └── src\n        └── main.rs\n","text",[124,214,211],{"__ignoreMap":215},"",[15,217,218,219,222],{},"Scaffolding in place, we need to add UniFFI to our workspace's ",[124,220,221],{},"Cargo.toml",".\nThis way we can keep the binding generator and library versions in sync.\nAt the time this article was originally published,\nproc macro features were still under active development.\nSince then, they have stabilized somewhat,\nand it might make sense to start using the crates.io releases.\nIf you want the newest features and fixes though,\nit's best to use git dependencies.\nTracking the latest commits can cause pain,\nbut it is pretty rewarding if you want access to the latest improvements,\nwhich do come quite fast.",[207,224,228],{"className":225,"code":226,"language":227,"meta":215,"style":215},"language-toml shiki shiki-themes github-light","[workspace]\n\nmembers = [\n    \"uniffi-bindgen\",\n    \"ferrostar\",\n]\nresolver = \"2\"\n\n[workspace.dependencies]\nuniffi = \"0.26.1\"\n","toml",[124,229,230,246,253,259,268,276,281,290,295,309],{"__ignoreMap":215},[231,232,235,239,243],"span",{"class":233,"line":234},"line",1,[231,236,238],{"class":237},"sgsFI","[",[231,240,242],{"class":241},"s7eDp","workspace",[231,244,245],{"class":237},"]\n",[231,247,249],{"class":233,"line":248},2,[231,250,252],{"emptyLinePlaceholder":251},true,"\n",[231,254,256],{"class":233,"line":255},3,[231,257,258],{"class":237},"members = [\n",[231,260,262,266],{"class":233,"line":261},4,[231,263,265],{"class":264},"sYBdl","    \"uniffi-bindgen\"",[231,267,88],{"class":237},[231,269,271,274],{"class":233,"line":270},5,[231,272,273],{"class":264},"    \"ferrostar\"",[231,275,88],{"class":237},[231,277,279],{"class":233,"line":278},6,[231,280,245],{"class":237},[231,282,284,287],{"class":233,"line":283},7,[231,285,286],{"class":237},"resolver = ",[231,288,289],{"class":264},"\"2\"\n",[231,291,293],{"class":233,"line":292},8,[231,294,252],{"emptyLinePlaceholder":251},[231,296,298,300,302,304,307],{"class":233,"line":297},9,[231,299,238],{"class":237},[231,301,242],{"class":241},[231,303,52],{"class":237},[231,305,306],{"class":241},"dependencies",[231,308,245],{"class":237},[231,310,312,315],{"class":233,"line":311},10,[231,313,314],{"class":237},"uniffi = ",[231,316,317],{"class":264},"\"0.26.1\"\n",[15,319,320,321,127,323,325,326,328,329,332],{},"Then, we can add it to the ",[124,322,194],{},[124,324,197],{}," crates respectively.\nIn the ",[124,327,194],{}," crate, we need the ",[124,330,331],{},"cli"," feature.",[207,334,336],{"className":225,"code":335,"language":227,"meta":215,"style":215},"[dependencies]\nuniffi = { workspace = true, features = [\"cli\"] }\n",[124,337,338,346],{"__ignoreMap":215},[231,339,340,342,344],{"class":233,"line":234},[231,341,238],{"class":237},[231,343,306],{"class":241},[231,345,245],{"class":237},[231,347,348,351,355,358,361],{"class":233,"line":248},[231,349,350],{"class":237},"uniffi = { workspace = ",[231,352,354],{"class":353},"sYu0t","true",[231,356,357],{"class":237},", features = [",[231,359,360],{"class":264},"\"cli\"",[231,362,363],{"class":237},"] }\n",[15,365,366,367,370,371,374],{},"The ",[124,368,369],{},"main"," function in ",[124,372,373],{},"uniffi-bindgen\u002Fsrc\u002Fmain.rs"," is a single line.\nHere's the entire file:",[207,376,380],{"className":377,"code":378,"language":379,"meta":215,"style":215},"language-rust shiki shiki-themes github-light","fn main() {\n    uniffi::uniffi_bindgen_main()\n}\n","rust",[124,381,382,394,408],{"__ignoreMap":215},[231,383,384,388,391],{"class":233,"line":234},[231,385,387],{"class":386},"sD7c4","fn",[231,389,390],{"class":241}," main",[231,392,393],{"class":237},"() {\n",[231,395,396,399,402,405],{"class":233,"line":248},[231,397,398],{"class":241},"    uniffi",[231,400,401],{"class":386},"::",[231,403,404],{"class":241},"uniffi_bindgen_main",[231,406,407],{"class":237},"()\n",[231,409,410],{"class":233,"line":255},[231,411,412],{"class":237},"}\n",[15,414,415,416,418,419,421,422,425,426,52],{},"In the ",[124,417,197],{}," crate, we also need ",[124,420,201],{}," as a normal dependency\n",[30,423,424],{},"and"," build dependency.\nWe also need to configure the library target with a few different\n",[34,427,430,433],{"href":428,"rel":429,"target":50},"https:\u002F\u002Fdoc.rust-lang.org\u002Freference\u002Flinkage.html",[49],[124,431,432],{},"crate_type","s",[435,436,437,444,454],"ul",{},[438,439,440,443],"li",{},[124,441,442],{},"cdylib"," - A dynamic system library. Used on most platforms.",[438,445,446,449,450,453],{},[124,447,448],{},"staticlib"," - A system library with ",[30,451,452],{},"all"," upstream dependencies included.\nRequired to target iOS.",[438,455,456,459,460,463],{},[124,457,458],{},"lib"," - A Rust library.\nThis isn't mentioned in the UniFFI documentation as of this writing,\nbut if you want to add integration tests (runnable via ",[124,461,462],{},"cargo test",")\nand you don't include a Rust library target,\nyou'll get strange errors.",[207,465,467],{"className":225,"code":466,"language":227,"meta":215,"style":215},"[dependencies]\nuniffi.workspace = true\n\n[build-dependencies]\nuniffi = { workspace = true, features = [\"build\"] }\n\n[lib]\ncrate-type = [\"cdylib\", \"staticlib\", \"lib\"]\n",[124,468,469,477,485,489,498,511,515,523],{"__ignoreMap":215},[231,470,471,473,475],{"class":233,"line":234},[231,472,238],{"class":237},[231,474,306],{"class":241},[231,476,245],{"class":237},[231,478,479,482],{"class":233,"line":248},[231,480,481],{"class":237},"uniffi.workspace = ",[231,483,484],{"class":353},"true\n",[231,486,487],{"class":233,"line":255},[231,488,252],{"emptyLinePlaceholder":251},[231,490,491,493,496],{"class":233,"line":261},[231,492,238],{"class":237},[231,494,495],{"class":241},"build-dependencies",[231,497,245],{"class":237},[231,499,500,502,504,506,509],{"class":233,"line":270},[231,501,350],{"class":237},[231,503,354],{"class":353},[231,505,357],{"class":237},[231,507,508],{"class":264},"\"build\"",[231,510,363],{"class":237},[231,512,513],{"class":233,"line":278},[231,514,252],{"emptyLinePlaceholder":251},[231,516,517,519,521],{"class":233,"line":283},[231,518,238],{"class":237},[231,520,458],{"class":241},[231,522,245],{"class":237},[231,524,525,528,531,534,537,539,542],{"class":233,"line":292},[231,526,527],{"class":237},"crate-type = [",[231,529,530],{"class":264},"\"cdylib\"",[231,532,533],{"class":237},", ",[231,535,536],{"class":264},"\"staticlib\"",[231,538,533],{"class":237},[231,540,541],{"class":264},"\"lib\"",[231,543,245],{"class":237},[182,545,547],{"id":546},"defining-what-to-export","Defining what to export",[15,549,550],{},"Internally, UniFFI uses an IDL (Interface Definition Language) to describe\nthe public interface of your crate to foreign code (ex: Swift and Kotlin).\nThis is similar to the process of writing a header file in C\nin that it is a set of definitions,\nbut the definition file is used to generate the foreign language bindings.",[15,552,553,554,556],{},"This used to require writing a bunch of boilerplate by hand.\nFortunately, UniFFI (starting from v0.25.0) automates this using procedural macros.\nFor our use case,\nwe were able to replace ",[30,555,452],{}," handwritten UDL with macros,\nand expect this will be the best path for most library authors going forward.",[558,559,561],"h4",{"id":560},"functions","Functions",[15,563,564,565,568],{},"Let's get started by exporting a function from Rust\nwhich generates a parser for routing data coming from our APIs.\nA single macro, ",[124,566,567],{},"uniffi:export"," is all we need to export a top-level function.",[207,570,572],{"className":377,"code":571,"language":379,"meta":215,"style":215},"#[uniffi::export]\nfn create_osrm_response_parser(polyline_precision: u32) -> Arc\u003Cdyn RouteResponseParser> {\n    Arc::new(OsrmResponseParser::new(polyline_precision))\n}\n",[124,573,574,584,621,644],{"__ignoreMap":215},[231,575,576,579,581],{"class":233,"line":234},[231,577,578],{"class":237},"#[uniffi",[231,580,401],{"class":386},[231,582,583],{"class":237},"export]\n",[231,585,586,588,591,594,597,600,603,606,609,612,615,618],{"class":233,"line":248},[231,587,387],{"class":386},[231,589,590],{"class":241}," create_osrm_response_parser",[231,592,593],{"class":237},"(polyline_precision",[231,595,596],{"class":386},":",[231,598,599],{"class":241}," u32",[231,601,602],{"class":237},") ",[231,604,605],{"class":386},"->",[231,607,608],{"class":241}," Arc",[231,610,611],{"class":237},"\u003C",[231,613,614],{"class":386},"dyn",[231,616,617],{"class":241}," RouteResponseParser",[231,619,620],{"class":237},"> {\n",[231,622,623,626,628,631,634,637,639,641],{"class":233,"line":255},[231,624,625],{"class":241},"    Arc",[231,627,401],{"class":386},[231,629,630],{"class":241},"new",[231,632,633],{"class":237},"(",[231,635,636],{"class":241},"OsrmResponseParser",[231,638,401],{"class":386},[231,640,630],{"class":241},[231,642,643],{"class":237},"(polyline_precision))\n",[231,645,646],{"class":233,"line":261},[231,647,412],{"class":237},[15,649,650,651,654,655,658,659,533,662,127,665,668,669,672,673,676],{},"UniFFI macros generate the definitions for us automatically\nbased on the function's type signature.\nMany built-in types like numbers, ",[124,652,653],{},"String",", and ",[124,656,657],{},"bool","\n\"just work\" as you expect.\nCollections like ",[124,660,661],{},"Vec\u003CT>",[124,663,664],{},"Option\u003CT>",[124,666,667],{},"HashMap\u003CK, V>"," are also implemented\nfor all types that UniFFI knows how to represent over the FFI.\nThey even have special cases for types like ",[124,670,671],{},"Vec\u003Cu8>","\nwhich idiomatically map to a language-specific byte sequence type\n(ex: ",[124,674,675],{},"Data"," in Swift)!",[15,678,679],{},"If anything you export references a type\nthat UniFFI doesn't know how to convert,\nyou will get a compilation error.\nSo, how do we tell UniFFI about new types?",[558,681,683],{"id":682},"data-models","Data models",[15,685,686,687,690],{},"Let's look at two data models: user locations and route requests.\nWe'll export these using derive macros.\nIf you've used the ",[124,688,689],{},"serde"," crate, this should feel familiar.",[15,692,693,694,697,698,701,702,705],{},"User location is just a struct with some properties like coordinates\nand course over ground.\nThe ",[124,695,696],{},"uniffi::Record"," macro exposes our Rust types idiomatically.\nThey will show up as ",[124,699,700],{},"struct","s in Swift, and ",[124,703,704],{},"data class","es in Kotlin.",[207,707,709],{"className":377,"code":708,"language":379,"meta":215,"style":215},"#[derive(uniffi::Record)]\npub struct UserLocation {\n    pub coordinates: GeographicCoordinates,\n    pub horizontal_accuracy: f64,\n    pub course_over_ground: Option\u003CCourseOverGround>,\n    pub timestamp: SystemTime,\n}\n",[124,710,711,724,738,753,767,787,801],{"__ignoreMap":215},[231,712,713,716,718,721],{"class":233,"line":234},[231,714,715],{"class":237},"#[derive(uniffi",[231,717,401],{"class":386},[231,719,720],{"class":241},"Record",[231,722,723],{"class":237},")]\n",[231,725,726,729,732,735],{"class":233,"line":248},[231,727,728],{"class":386},"pub",[231,730,731],{"class":386}," struct",[231,733,734],{"class":241}," UserLocation",[231,736,737],{"class":237}," {\n",[231,739,740,743,746,748,751],{"class":233,"line":255},[231,741,742],{"class":386},"    pub",[231,744,745],{"class":237}," coordinates",[231,747,596],{"class":386},[231,749,750],{"class":241}," GeographicCoordinates",[231,752,88],{"class":237},[231,754,755,757,760,762,765],{"class":233,"line":261},[231,756,742],{"class":386},[231,758,759],{"class":237}," horizontal_accuracy",[231,761,596],{"class":386},[231,763,764],{"class":241}," f64",[231,766,88],{"class":237},[231,768,769,771,774,776,779,781,784],{"class":233,"line":270},[231,770,742],{"class":386},[231,772,773],{"class":237}," course_over_ground",[231,775,596],{"class":386},[231,777,778],{"class":241}," Option",[231,780,611],{"class":237},[231,782,783],{"class":241},"CourseOverGround",[231,785,786],{"class":237},">,\n",[231,788,789,791,794,796,799],{"class":233,"line":278},[231,790,742],{"class":386},[231,792,793],{"class":237}," timestamp",[231,795,596],{"class":386},[231,797,798],{"class":241}," SystemTime",[231,800,88],{"class":237},[231,802,803],{"class":233,"line":283},[231,804,412],{"class":237},[15,806,807],{},"Route requests are a little different.\nThey don't really map to a record type.\nSince we want to build an extensible framework,\nwe need to account for multiple ways of getting a route.\nFor example, making an HTTP request, or computing a route on-device.",[15,809,810,811,814],{},"This maps well to an enumeration.\nThe ",[124,812,813],{},"uniffi::Enum"," derive macro exports these like you would expect.",[207,816,818],{"className":377,"code":817,"language":379,"meta":215,"style":215},"#[derive(uniffi::Enum)]\npub enum RouteRequest {\n    HttpPost {\n        url: String,\n        headers: HashMap\u003CString, String>,\n        body: Vec\u003Cu8>,\n    },\n    \u002F\u002F ...\n}\n",[124,819,820,831,843,850,862,882,899,904,910],{"__ignoreMap":215},[231,821,822,824,826,829],{"class":233,"line":234},[231,823,715],{"class":237},[231,825,401],{"class":386},[231,827,828],{"class":241},"Enum",[231,830,723],{"class":237},[231,832,833,835,838,841],{"class":233,"line":248},[231,834,728],{"class":386},[231,836,837],{"class":386}," enum",[231,839,840],{"class":241}," RouteRequest",[231,842,737],{"class":237},[231,844,845,848],{"class":233,"line":255},[231,846,847],{"class":241},"    HttpPost",[231,849,737],{"class":237},[231,851,852,855,857,860],{"class":233,"line":261},[231,853,854],{"class":237},"        url",[231,856,596],{"class":386},[231,858,859],{"class":241}," String",[231,861,88],{"class":237},[231,863,864,867,869,872,874,876,878,880],{"class":233,"line":270},[231,865,866],{"class":237},"        headers",[231,868,596],{"class":386},[231,870,871],{"class":241}," HashMap",[231,873,611],{"class":237},[231,875,653],{"class":241},[231,877,533],{"class":237},[231,879,653],{"class":241},[231,881,786],{"class":237},[231,883,884,887,889,892,894,897],{"class":233,"line":278},[231,885,886],{"class":237},"        body",[231,888,596],{"class":386},[231,890,891],{"class":241}," Vec",[231,893,611],{"class":237},[231,895,896],{"class":241},"u8",[231,898,786],{"class":237},[231,900,901],{"class":233,"line":283},[231,902,903],{"class":237},"    },\n",[231,905,906],{"class":233,"line":292},[231,907,909],{"class":908},"sAwPA","    \u002F\u002F ...\n",[231,911,912],{"class":233,"line":297},[231,913,412],{"class":237},[558,915,917],{"id":916},"objects","Objects",[15,919,920],{},"You can also export more complex objects\nwith local state, methods, and constructors!",[15,922,923],{},"One example of this in Ferrostar is our route adapter.\nIt stores a request generator and response parser\nand exposes a generic interface\nso the caller doesn't need to know about the details.",[207,925,927],{"className":377,"code":926,"language":379,"meta":215,"style":215},"#[derive(uniffi::Object)]\npub struct RouteAdapter {\n    request_generator: Arc\u003Cdyn RouteRequestGenerator>,\n    response_parser: Arc\u003Cdyn RouteResponseParser>,\n}\n\n#[uniffi::export]\nimpl RouteAdapter {\n    #[uniffi::constructor]\n    pub fn new(\n        request_generator: Arc\u003Cdyn RouteRequestGenerator>,\n        response_parser: Arc\u003Cdyn RouteResponseParser>,\n    ) -> Self {\n        Self {\n            request_generator,\n            response_parser,\n        }\n    }\n\n    #[uniffi::constructor]\n    pub fn new_valhalla_http(endpoint_url: String, profile: String) -> Arc\u003CSelf> {\n        let request_generator = create_valhalla_request_generator(endpoint_url, profile);\n        let response_parser = create_osrm_response_parser(6);\n        Self::new(request_generator, response_parser)\n    }\n\n    pub fn generate_request(\n        &self,\n        user_location: UserLocation,\n        waypoints: Vec\u003CGeographicCoordinates>,\n    ) -> Result\u003CRouteRequest, RoutingRequestGenerationError> {\n        self.request_generator\n            .generate_request(user_location, waypoints)\n    }\n\n    pub fn parse_response(\n        &self,\n        response: Vec\u003Cu8>,\n    ) -> Result\u003CVec\u003CRoute>, RoutingResponseParseError> {\n        self.response_parser.parse_response(response)\n    }\n}\n",[124,928,929,940,951,969,986,990,994,1002,1011,1021,1034,1052,1070,1083,1091,1097,1103,1109,1115,1120,1129,1166,1184,1204,1216,1221,1226,1238,1249,1261,1278,1300,1311,1323,1328,1333,1345,1354,1370,1397,1415,1420],{"__ignoreMap":215},[231,930,931,933,935,938],{"class":233,"line":234},[231,932,715],{"class":237},[231,934,401],{"class":386},[231,936,937],{"class":241},"Object",[231,939,723],{"class":237},[231,941,942,944,946,949],{"class":233,"line":248},[231,943,728],{"class":386},[231,945,731],{"class":386},[231,947,948],{"class":241}," RouteAdapter",[231,950,737],{"class":237},[231,952,953,956,958,960,962,964,967],{"class":233,"line":255},[231,954,955],{"class":237},"    request_generator",[231,957,596],{"class":386},[231,959,608],{"class":241},[231,961,611],{"class":237},[231,963,614],{"class":386},[231,965,966],{"class":241}," RouteRequestGenerator",[231,968,786],{"class":237},[231,970,971,974,976,978,980,982,984],{"class":233,"line":261},[231,972,973],{"class":237},"    response_parser",[231,975,596],{"class":386},[231,977,608],{"class":241},[231,979,611],{"class":237},[231,981,614],{"class":386},[231,983,617],{"class":241},[231,985,786],{"class":237},[231,987,988],{"class":233,"line":270},[231,989,412],{"class":237},[231,991,992],{"class":233,"line":278},[231,993,252],{"emptyLinePlaceholder":251},[231,995,996,998,1000],{"class":233,"line":283},[231,997,578],{"class":237},[231,999,401],{"class":386},[231,1001,583],{"class":237},[231,1003,1004,1007,1009],{"class":233,"line":292},[231,1005,1006],{"class":386},"impl",[231,1008,948],{"class":241},[231,1010,737],{"class":237},[231,1012,1013,1016,1018],{"class":233,"line":297},[231,1014,1015],{"class":237},"    #[uniffi",[231,1017,401],{"class":386},[231,1019,1020],{"class":237},"constructor]\n",[231,1022,1023,1025,1028,1031],{"class":233,"line":311},[231,1024,742],{"class":386},[231,1026,1027],{"class":386}," fn",[231,1029,1030],{"class":241}," new",[231,1032,1033],{"class":237},"(\n",[231,1035,1037,1040,1042,1044,1046,1048,1050],{"class":233,"line":1036},11,[231,1038,1039],{"class":237},"        request_generator",[231,1041,596],{"class":386},[231,1043,608],{"class":241},[231,1045,611],{"class":237},[231,1047,614],{"class":386},[231,1049,966],{"class":241},[231,1051,786],{"class":237},[231,1053,1055,1058,1060,1062,1064,1066,1068],{"class":233,"line":1054},12,[231,1056,1057],{"class":237},"        response_parser",[231,1059,596],{"class":386},[231,1061,608],{"class":241},[231,1063,611],{"class":237},[231,1065,614],{"class":386},[231,1067,617],{"class":241},[231,1069,786],{"class":237},[231,1071,1073,1076,1078,1081],{"class":233,"line":1072},13,[231,1074,1075],{"class":237},"    ) ",[231,1077,605],{"class":386},[231,1079,1080],{"class":353}," Self",[231,1082,737],{"class":237},[231,1084,1086,1089],{"class":233,"line":1085},14,[231,1087,1088],{"class":353},"        Self",[231,1090,737],{"class":237},[231,1092,1094],{"class":233,"line":1093},15,[231,1095,1096],{"class":237},"            request_generator,\n",[231,1098,1100],{"class":233,"line":1099},16,[231,1101,1102],{"class":237},"            response_parser,\n",[231,1104,1106],{"class":233,"line":1105},17,[231,1107,1108],{"class":237},"        }\n",[231,1110,1112],{"class":233,"line":1111},18,[231,1113,1114],{"class":237},"    }\n",[231,1116,1118],{"class":233,"line":1117},19,[231,1119,252],{"emptyLinePlaceholder":251},[231,1121,1123,1125,1127],{"class":233,"line":1122},20,[231,1124,1015],{"class":237},[231,1126,401],{"class":386},[231,1128,1020],{"class":237},[231,1130,1132,1134,1136,1139,1142,1144,1146,1149,1151,1153,1155,1157,1159,1161,1164],{"class":233,"line":1131},21,[231,1133,742],{"class":386},[231,1135,1027],{"class":386},[231,1137,1138],{"class":241}," new_valhalla_http",[231,1140,1141],{"class":237},"(endpoint_url",[231,1143,596],{"class":386},[231,1145,859],{"class":241},[231,1147,1148],{"class":237},", profile",[231,1150,596],{"class":386},[231,1152,859],{"class":241},[231,1154,602],{"class":237},[231,1156,605],{"class":386},[231,1158,608],{"class":241},[231,1160,611],{"class":237},[231,1162,1163],{"class":353},"Self",[231,1165,620],{"class":237},[231,1167,1169,1172,1175,1178,1181],{"class":233,"line":1168},22,[231,1170,1171],{"class":386},"        let",[231,1173,1174],{"class":237}," request_generator ",[231,1176,1177],{"class":386},"=",[231,1179,1180],{"class":241}," create_valhalla_request_generator",[231,1182,1183],{"class":237},"(endpoint_url, profile);\n",[231,1185,1187,1189,1192,1194,1196,1198,1201],{"class":233,"line":1186},23,[231,1188,1171],{"class":386},[231,1190,1191],{"class":237}," response_parser ",[231,1193,1177],{"class":386},[231,1195,590],{"class":241},[231,1197,633],{"class":237},[231,1199,1200],{"class":353},"6",[231,1202,1203],{"class":237},");\n",[231,1205,1207,1209,1211,1213],{"class":233,"line":1206},24,[231,1208,1088],{"class":353},[231,1210,401],{"class":386},[231,1212,630],{"class":241},[231,1214,1215],{"class":237},"(request_generator, response_parser)\n",[231,1217,1219],{"class":233,"line":1218},25,[231,1220,1114],{"class":237},[231,1222,1224],{"class":233,"line":1223},26,[231,1225,252],{"emptyLinePlaceholder":251},[231,1227,1229,1231,1233,1236],{"class":233,"line":1228},27,[231,1230,742],{"class":386},[231,1232,1027],{"class":386},[231,1234,1235],{"class":241}," generate_request",[231,1237,1033],{"class":237},[231,1239,1241,1244,1247],{"class":233,"line":1240},28,[231,1242,1243],{"class":386},"        &",[231,1245,1246],{"class":353},"self",[231,1248,88],{"class":237},[231,1250,1252,1255,1257,1259],{"class":233,"line":1251},29,[231,1253,1254],{"class":237},"        user_location",[231,1256,596],{"class":386},[231,1258,734],{"class":241},[231,1260,88],{"class":237},[231,1262,1264,1267,1269,1271,1273,1276],{"class":233,"line":1263},30,[231,1265,1266],{"class":237},"        waypoints",[231,1268,596],{"class":386},[231,1270,891],{"class":241},[231,1272,611],{"class":237},[231,1274,1275],{"class":241},"GeographicCoordinates",[231,1277,786],{"class":237},[231,1279,1281,1283,1285,1288,1290,1293,1295,1298],{"class":233,"line":1280},31,[231,1282,1075],{"class":237},[231,1284,605],{"class":386},[231,1286,1287],{"class":241}," Result",[231,1289,611],{"class":237},[231,1291,1292],{"class":241},"RouteRequest",[231,1294,533],{"class":237},[231,1296,1297],{"class":241},"RoutingRequestGenerationError",[231,1299,620],{"class":237},[231,1301,1303,1306,1308],{"class":233,"line":1302},32,[231,1304,1305],{"class":353},"        self",[231,1307,52],{"class":386},[231,1309,1310],{"class":237},"request_generator\n",[231,1312,1314,1317,1320],{"class":233,"line":1313},33,[231,1315,1316],{"class":386},"            .",[231,1318,1319],{"class":241},"generate_request",[231,1321,1322],{"class":237},"(user_location, waypoints)\n",[231,1324,1326],{"class":233,"line":1325},34,[231,1327,1114],{"class":237},[231,1329,1331],{"class":233,"line":1330},35,[231,1332,252],{"emptyLinePlaceholder":251},[231,1334,1336,1338,1340,1343],{"class":233,"line":1335},36,[231,1337,742],{"class":386},[231,1339,1027],{"class":386},[231,1341,1342],{"class":241}," parse_response",[231,1344,1033],{"class":237},[231,1346,1348,1350,1352],{"class":233,"line":1347},37,[231,1349,1243],{"class":386},[231,1351,1246],{"class":353},[231,1353,88],{"class":237},[231,1355,1357,1360,1362,1364,1366,1368],{"class":233,"line":1356},38,[231,1358,1359],{"class":237},"        response",[231,1361,596],{"class":386},[231,1363,891],{"class":241},[231,1365,611],{"class":237},[231,1367,896],{"class":241},[231,1369,786],{"class":237},[231,1371,1373,1375,1377,1379,1381,1384,1386,1389,1392,1395],{"class":233,"line":1372},39,[231,1374,1075],{"class":237},[231,1376,605],{"class":386},[231,1378,1287],{"class":241},[231,1380,611],{"class":237},[231,1382,1383],{"class":241},"Vec",[231,1385,611],{"class":237},[231,1387,1388],{"class":241},"Route",[231,1390,1391],{"class":237},">, ",[231,1393,1394],{"class":241},"RoutingResponseParseError",[231,1396,620],{"class":237},[231,1398,1400,1402,1404,1407,1409,1412],{"class":233,"line":1399},40,[231,1401,1305],{"class":353},[231,1403,52],{"class":386},[231,1405,1406],{"class":237},"response_parser",[231,1408,52],{"class":386},[231,1410,1411],{"class":241},"parse_response",[231,1413,1414],{"class":237},"(response)\n",[231,1416,1418],{"class":233,"line":1417},41,[231,1419,1114],{"class":237},[231,1421,1423],{"class":233,"line":1422},42,[231,1424,412],{"class":237},[15,1426,1427,1428,1431,1432,1434],{},"This example is a bit more complicated than the others we've looked at so far.\nFirst, we use the ",[124,1429,1430],{},"uniffi::Object"," derive macro on the struct.\nIn contrast to ",[124,1433,696],{},",\nthis signals that the type will be passed by reference and may have methods.\nThis will be exposed in Swift or Kotlin as a class.",[15,1436,1437,1438,1441,1442,1445,1446,1448,1449,1452],{},"You must annotate your constructors with the ",[124,1439,1440],{},"uniffi::constructor"," macro.\nAt the time of this writing, constructors must return an ",[124,1443,1444],{},"Arc\u003CSelf>",".\nThe ",[124,1447,630],{}," constructor is special, and is exposed most naturally,\nbut other named constructors are also supported if you mark them as such.\nThey are not currently as idiomatic as the default constructor,\nbut I expect things like exposing a ",[124,1450,1451],{},"convenience init"," to Swift\nwill be possible in the future.",[15,1454,1455,1456,1458,1459,1462,1463,1466,1467,1470,1471,1474,1475,1478],{},"Finally, types exported with ",[124,1457,1430],{}," tell the binding generator\nto emit a ",[124,1460,1461],{},"protocol"," (Swift) or ",[124,1464,1465],{},"interface"," (Kotlin) definition.\n",[140,1468,1469],{},"Pro tip:"," This makes it easier for you to write mock implementations\nfor unit testing!\nOur rule of thumb is to ",[30,1472,1473],{},"pass"," the protocol\u002Finterface to your native methods,\nand define your instance variables with protocol\u002Finterface types.\nWhen the time comes to construct one,\nyour business logic will dictate which ",[30,1476,1477],{},"concrete"," implementation to construct.\nThis makes your code significantly more extensible,\nand allows you to either use implementations from the Rust core\nor define your own in Swift\u002FKotlin.",[558,1480,1482],{"id":1481},"errors","Errors",[15,1484,1485],{},"Result types in Rust signal errors,\nand UniFFI can expose these idiomatically in the generated bindings.\nWe use this pattern in the above example to signal response parsing errors.",[207,1487,1489],{"className":377,"code":1488,"language":379,"meta":215,"style":215},"#[derive(Debug, thiserror::Error, uniffi::Error)]\npub enum RoutingResponseParseError {\n    #[error(\"Failed to parse route response: {error}.\")]\n    ParseError { error: String },\n    \u002F\u002F ...\n    #[error(\"An unknown error parsing a response was raised in foreign code.\")]\n    UnknownError,\n}\n",[124,1490,1491,1516,1527,1537,1552,1556,1565,1572],{"__ignoreMap":215},[231,1492,1493,1496,1499,1502,1504,1507,1510,1512,1514],{"class":233,"line":234},[231,1494,1495],{"class":237},"#[derive(",[231,1497,1498],{"class":241},"Debug",[231,1500,1501],{"class":237},", thiserror",[231,1503,401],{"class":386},[231,1505,1506],{"class":241},"Error",[231,1508,1509],{"class":237},", uniffi",[231,1511,401],{"class":386},[231,1513,1506],{"class":241},[231,1515,723],{"class":237},[231,1517,1518,1520,1522,1525],{"class":233,"line":248},[231,1519,728],{"class":386},[231,1521,837],{"class":386},[231,1523,1524],{"class":241}," RoutingResponseParseError",[231,1526,737],{"class":237},[231,1528,1529,1532,1535],{"class":233,"line":255},[231,1530,1531],{"class":237},"    #[error(",[231,1533,1534],{"class":264},"\"Failed to parse route response: {error}.\"",[231,1536,723],{"class":237},[231,1538,1539,1542,1545,1547,1549],{"class":233,"line":261},[231,1540,1541],{"class":241},"    ParseError",[231,1543,1544],{"class":237}," { error",[231,1546,596],{"class":386},[231,1548,859],{"class":241},[231,1550,1551],{"class":237}," },\n",[231,1553,1554],{"class":233,"line":270},[231,1555,909],{"class":908},[231,1557,1558,1560,1563],{"class":233,"line":278},[231,1559,1531],{"class":237},[231,1561,1562],{"class":264},"\"An unknown error parsing a response was raised in foreign code.\"",[231,1564,723],{"class":237},[231,1566,1567,1570],{"class":233,"line":283},[231,1568,1569],{"class":241},"    UnknownError",[231,1571,88],{"class":237},[231,1573,1574],{"class":233,"line":292},[231,1575,412],{"class":237},[15,1577,366,1578,1581,1582,1585,1586,1589,1590,1592,1593,1596],{},[124,1579,1580],{},"uniffi::Error"," derive macro will work with any type\nconforming to ",[124,1583,1584],{},"std::Error",".\nWe make extensive use of ",[124,1587,1588],{},"thiserror"," for simplicity.\nIn Swift and Kotlin code,\nthis definition will be bridged into an enum conforming to ",[124,1591,1506],{},"\nand a sealed subclass of ",[124,1594,1595],{},"Exception"," respectively.",[15,1598,1599],{},"Besides exposing the error types themselves,\nUniFFI also makes the error handling idiomatic.\nLet's return to the response parsing function signature.",[207,1601,1603],{"className":377,"code":1602,"language":379,"meta":215,"style":215},"pub fn parse_response(\n    &self,\n    response: Vec\u003Cu8>,\n) -> Result\u003CVec\u003CRoute>, RoutingResponseParseError>\n",[124,1604,1605,1615,1624,1639],{"__ignoreMap":215},[231,1606,1607,1609,1611,1613],{"class":233,"line":234},[231,1608,728],{"class":386},[231,1610,1027],{"class":386},[231,1612,1342],{"class":241},[231,1614,1033],{"class":237},[231,1616,1617,1620,1622],{"class":233,"line":248},[231,1618,1619],{"class":386},"    &",[231,1621,1246],{"class":353},[231,1623,88],{"class":237},[231,1625,1626,1629,1631,1633,1635,1637],{"class":233,"line":255},[231,1627,1628],{"class":237},"    response",[231,1630,596],{"class":386},[231,1632,891],{"class":241},[231,1634,611],{"class":237},[231,1636,896],{"class":241},[231,1638,786],{"class":237},[231,1640,1641,1643,1645,1647,1649,1651,1653,1655,1657,1659],{"class":233,"line":261},[231,1642,602],{"class":237},[231,1644,605],{"class":386},[231,1646,1287],{"class":241},[231,1648,611],{"class":237},[231,1650,1383],{"class":241},[231,1652,611],{"class":237},[231,1654,1388],{"class":241},[231,1656,1391],{"class":237},[231,1658,1394],{"class":241},[231,1660,1661],{"class":237},">\n",[15,1663,1664,1665,1667],{},"Semantically speaking,\nthis function will either return an ordered list of routes,\nor it will generate an error of type ",[124,1666,1394],{},".\nAs such, this is how the generated Swift bindings will look:",[207,1669,1673],{"className":1670,"code":1671,"language":1672,"meta":215,"style":215},"language-swift shiki shiki-themes github-light","public func parseResponse(response: Data) throws -> [Route]\n","swift",[124,1674,1675],{"__ignoreMap":215},[231,1676,1677,1680,1683,1686,1688,1691,1694,1697,1700],{"class":233,"line":234},[231,1678,1679],{"class":386},"public",[231,1681,1682],{"class":386}," func",[231,1684,1685],{"class":241}," parseResponse",[231,1687,633],{"class":237},[231,1689,1690],{"class":241},"response",[231,1692,1693],{"class":237},": Data) ",[231,1695,1696],{"class":386},"throws",[231,1698,1699],{"class":386}," ->",[231,1701,1702],{"class":237}," [Route]\n",[15,1704,1705,1706,1708],{},"Swift does not explicitly list error types, but uses the ",[124,1707,1696],{}," keyword.\nIf you inspect the generated code,\nyou can see that UniFFI does indeed handle error variants,\nmapping them to your error types.",[558,1710,1712],{"id":1711},"traits","Traits",[15,1714,1715],{},"Carrying on the motivating use case of vendor-agnostic routing API integration,\nwe define traits in our crate for response parsing\nand similar things that we want to ensure remain generic.",[207,1717,1719],{"className":377,"code":1718,"language":379,"meta":215,"style":215},"#[uniffi::export(with_foreign)]\npub trait RouteResponseParser: Send + Sync {\n    \u002F\u002F\u002F Parses a raw response from the routing backend into a route.\n    fn parse_response(&self, response: Vec\u003Cu8>) -> Result\u003CVec\u003CRoute>, RoutingResponseParseError>;\n}\n",[124,1720,1721,1730,1752,1757,1804],{"__ignoreMap":215},[231,1722,1723,1725,1727],{"class":233,"line":234},[231,1724,578],{"class":237},[231,1726,401],{"class":386},[231,1728,1729],{"class":237},"export(with_foreign)]\n",[231,1731,1732,1734,1737,1739,1741,1744,1747,1750],{"class":233,"line":248},[231,1733,728],{"class":386},[231,1735,1736],{"class":386}," trait",[231,1738,617],{"class":241},[231,1740,596],{"class":386},[231,1742,1743],{"class":241}," Send",[231,1745,1746],{"class":386}," +",[231,1748,1749],{"class":241}," Sync",[231,1751,737],{"class":237},[231,1753,1754],{"class":233,"line":255},[231,1755,1756],{"class":908},"    \u002F\u002F\u002F Parses a raw response from the routing backend into a route.\n",[231,1758,1759,1762,1764,1766,1769,1771,1774,1776,1778,1780,1782,1785,1787,1789,1791,1793,1795,1797,1799,1801],{"class":233,"line":261},[231,1760,1761],{"class":386},"    fn",[231,1763,1342],{"class":241},[231,1765,633],{"class":237},[231,1767,1768],{"class":386},"&",[231,1770,1246],{"class":353},[231,1772,1773],{"class":237},", response",[231,1775,596],{"class":386},[231,1777,891],{"class":241},[231,1779,611],{"class":237},[231,1781,896],{"class":241},[231,1783,1784],{"class":237},">) ",[231,1786,605],{"class":386},[231,1788,1287],{"class":241},[231,1790,611],{"class":237},[231,1792,1383],{"class":241},[231,1794,611],{"class":237},[231,1796,1388],{"class":241},[231,1798,1391],{"class":237},[231,1800,1394],{"class":241},[231,1802,1803],{"class":237},">;\n",[231,1805,1806],{"class":233,"line":270},[231,1807,412],{"class":237},[15,1809,1810,1811,1814,1815,1818,1819,1822],{},"You can export traits with the ",[124,1812,1813],{},"uniffi::export"," macro,\nand will they show up as protocols\u002Finterfaces in your foreign code.\nEven better, if you use ",[124,1816,1817],{},"with_foreign",", you can supply foreign code to Rust!\nIt requires a bit of a dance Rust-side\n(you must use types of the form ",[124,1820,1821],{},"Arc\u003Cdyn Trait>"," at the time of this writing),\nbut that's a small price to pay for such flexibility.",[15,1824,1825,1826,1829,1830,1832,1833,1835],{},"Here's what it looks like in Swift.\nFirst, the Rust ",[124,1827,1828],{},"trait"," is translated into a Swift ",[124,1831,1461],{}," automatically\nby the UniFFI binding generator.\nThe method even has ",[124,1834,1696],{},", just like you'd write in a pure Swift project!",[207,1837,1839],{"className":1670,"code":1838,"language":1672,"meta":215,"style":215},"public protocol RouteResponseParser: AnyObject {\n    \u002F**\n     * Parses a raw response from the routing backend into a route.\n     *\n     * We use a sequence of octets as a common interchange format.\n     * as this works for all currently conceivable formats (JSON, PBF, etc.).\n     *\u002F\n    func parseResponse(response: Data) throws -> [Route]\n}\n",[124,1840,1841,1858,1863,1868,1873,1878,1883,1888,1907],{"__ignoreMap":215},[231,1842,1843,1845,1848,1850,1853,1856],{"class":233,"line":234},[231,1844,1679],{"class":386},[231,1846,1847],{"class":386}," protocol",[231,1849,617],{"class":241},[231,1851,1852],{"class":237},": ",[231,1854,1855],{"class":353},"AnyObject",[231,1857,737],{"class":237},[231,1859,1860],{"class":233,"line":248},[231,1861,1862],{"class":908},"    \u002F**\n",[231,1864,1865],{"class":233,"line":255},[231,1866,1867],{"class":908},"     * Parses a raw response from the routing backend into a route.\n",[231,1869,1870],{"class":233,"line":261},[231,1871,1872],{"class":908},"     *\n",[231,1874,1875],{"class":233,"line":270},[231,1876,1877],{"class":908},"     * We use a sequence of octets as a common interchange format.\n",[231,1879,1880],{"class":233,"line":278},[231,1881,1882],{"class":908},"     * as this works for all currently conceivable formats (JSON, PBF, etc.).\n",[231,1884,1885],{"class":233,"line":283},[231,1886,1887],{"class":908},"     *\u002F\n",[231,1889,1890,1893,1895,1897,1899,1901,1903,1905],{"class":233,"line":292},[231,1891,1892],{"class":386},"    func",[231,1894,1685],{"class":241},[231,1896,633],{"class":237},[231,1898,1690],{"class":241},[231,1900,1693],{"class":237},[231,1902,1696],{"class":386},[231,1904,1699],{"class":386},[231,1906,1702],{"class":237},[231,1908,1909],{"class":233,"line":297},[231,1910,412],{"class":237},[15,1912,1913,1914,1917,1918,1920],{},"Now we can implement it just like any other protocol.\nHere's an example of an implementation that we use in our testing.\nIt's a mock that is initialized with a fixed set of routes for\n\"end-to-end\" tests without hitting a network service.\nIn the real world, you might use ",[124,1915,1916],{},"Codable","\nto convert the raw data into a Swift model\nand then map it to Ferrostar ",[124,1919,1388],{},"s.",[207,1922,1924],{"className":1670,"code":1923,"language":1672,"meta":215,"style":215},"class MockRouteResponseParser: RouteResponseParser {\n    private let routes: [Route]\n\n    init(routes: [Route]) {\n        self.routes = routes\n    }\n\n    func parseResponse(response _: Data) throws -> [Route] {\n        routes\n    }\n}\n",[124,1925,1926,1942,1953,1957,1970,1982,1986,1990,2010,2015,2019],{"__ignoreMap":215},[231,1927,1928,1931,1934,1936,1939],{"class":233,"line":234},[231,1929,1930],{"class":386},"class",[231,1932,1933],{"class":241}," MockRouteResponseParser",[231,1935,1852],{"class":237},[231,1937,1938],{"class":241},"RouteResponseParser ",[231,1940,1941],{"class":237},"{\n",[231,1943,1944,1947,1950],{"class":233,"line":248},[231,1945,1946],{"class":386},"    private",[231,1948,1949],{"class":386}," let",[231,1951,1952],{"class":237}," routes: [Route]\n",[231,1954,1955],{"class":233,"line":255},[231,1956,252],{"emptyLinePlaceholder":251},[231,1958,1959,1962,1964,1967],{"class":233,"line":261},[231,1960,1961],{"class":386},"    init",[231,1963,633],{"class":237},[231,1965,1966],{"class":241},"routes",[231,1968,1969],{"class":237},": [Route]) {\n",[231,1971,1972,1974,1977,1979],{"class":233,"line":270},[231,1973,1305],{"class":353},[231,1975,1976],{"class":237},".routes ",[231,1978,1177],{"class":386},[231,1980,1981],{"class":237}," routes\n",[231,1983,1984],{"class":233,"line":278},[231,1985,1114],{"class":237},[231,1987,1988],{"class":233,"line":283},[231,1989,252],{"emptyLinePlaceholder":251},[231,1991,1992,1994,1996,1998,2000,2003,2005,2007],{"class":233,"line":292},[231,1993,1892],{"class":386},[231,1995,1685],{"class":241},[231,1997,633],{"class":237},[231,1999,1690],{"class":241},[231,2001,2002],{"class":237}," _: Data) ",[231,2004,1696],{"class":386},[231,2006,1699],{"class":386},[231,2008,2009],{"class":237}," [Route] {\n",[231,2011,2012],{"class":233,"line":297},[231,2013,2014],{"class":237},"        routes\n",[231,2016,2017],{"class":233,"line":311},[231,2018,1114],{"class":237},[231,2020,2021],{"class":233,"line":1036},[231,2022,412],{"class":237},[15,2024,2025,2026,127,2029,2032],{},"By making things like ",[124,2027,2028],{},"RouteRequestGenerator",[124,2030,2031],{},"RouteResponseParser"," traits,\nwe make it possible for anyone to do the following:",[2034,2035,2036,2039,2042],"ol",{},[438,2037,2038],{},"Connect to a custom routing server (for example, one they are self-hosting).",[438,2040,2041],{},"Parse routes in a different format.",[438,2043,2044],{},"Generate routes on-device (in another library, Swift, Kotlin, etc.).",[15,2046,2047],{},"We're really excited about how this will enable more innovation by developers,\nsince Ferrostar will be easily adaptable to custom routing backends,\nboth for research and commercial use.",[22,2049,2051],{"id":2050},"what-works-well","What works well?",[15,2053,2054,2055,2057],{},"The APIs UniFFI generates truly feel idiomatic!\nIt's hard to overstate how nice this is.\nIt also goes out of its way to generate protocols\u002Finterfaces,\nwhich make it easy to mock parts for unit testing in native code,\nand even has pleasant surprises\nlike ",[124,2056,671],{}," mapping to idiomatic byte sequence types.",[15,2059,2060,2061,2064],{},"UniFFI encourages good hygiene all the way from Rust to your application code\nvia type safety.\nAny type that appears in your signature must be exported,\nso you get checks from end to end.\nAnd Rust functions that return a ",[124,2062,2063],{},"Result"," type\nare translated into idiomatic errors and exceptions.",[15,2066,2067,2068,2073],{},"You can even bridge custom types!\nWe don't have space to go deep into that here,\nbut here's some documentation on ",[34,2069,2072],{"href":2070,"rel":2071,"target":50},"https:\u002F\u002Fmozilla.github.io\u002Funiffi-rs\u002Flatest\u002Ftypes\u002Fcustom_types.html",[49],"how you can do it","\nwithout using primitives as a lowest common denominator.",[15,2075,2076],{},"Finally, UniFFI makes it easy to use native code implementations\nof a protocol\u002Finterface with your Rust core!\nThis lets us do things like dependency injection in a type-safe manner\nacross an FFI boundary.",[22,2078,2080],{"id":2079},"what-are-the-rough-edges","What are the rough edges?",[15,2082,2083],{},"When I gave a talk about this three months ago at the Seoul Rust meetup,\nand I had an entire slide full of challenges.\nAlmost all of them were related to UDL,\nand almost are non-issues when you replace UDL generation with proc macros!\nA few remain though.",[15,2085,2086,2087,2090,2091,2093,2094,127,2097,2100],{},"First, ",[140,2088,2089],{},"you'll need to change your habits around struct mutation",".\nSince we are introducing foreign code,\nUniFFI must assume that objects may be mutated from other threads.\nSo, you can't have mutable references to ",[124,2092,1246],{}," in UniFFI interfaces\nand must rely on interior mutability patterns instead.\nImportantly, your storage needs to be both ",[124,2095,2096],{},"Send",[124,2098,2099],{},"Sync",",\nwhich means you'll need to turn to things like atomics and mutexes.",[15,2102,2103,2104,2106,2107,2110],{},"UniFFI is also evolving quite rapidly.\nWhile some high-profile crates are fairly stable pre-1.0,\nyou can definitely expect some turbulence with UniFFI.\nThe changelogs are normally quite good,\nbut if you're tracking ",[124,2105,369],{},"\nyou can expect breaking changes.\nFortunately, the team are ",[30,2108,2109],{},"super"," responsive on GitHub.\nThey're working on it every day, and are extremely responsive as maintainers\n(we submitted a PR to improve some docs around proc macros,\nand it was merged after a round of discussions in less than 24 hours).",[15,2112,2113],{},"The process of generating bindings for Swift and Kotlin,\nintegrating this into your build tooling,\nand packaging everything into a usable SPM \u002F Maven package\nis also quite complex.\nIn fact, we would even suggest that the best practices around this\nare still being explored.\nThis is a complex topic, and this post is already quite long,\nso we'll save it for the next article.",[22,2115,2117],{"id":2116},"wrap-up","Wrap-up",[15,2119,2120,2121,2125],{},"In summary, while it is still a developing field,\nwe couldn't be more excited about the future of Rust on mobile.\nAs we've shown in this post, a shared core is not just possible;\nit's actually pretty straightforward and the developer experience is great\nthanks to UniFFI.\nWe've put what we've learned into a\n",[34,2122,2124],{"href":146,"rel":2123,"target":50},[49],"template repo","\nso you can try it too, without spending days figuring out boilerplate.",[15,2127,2128],{},"Stay tuned for the next post,\nwhere we'll dive deeper into build processes and packaging.\nYou can follow us on social media,\njoin our Slack or Discord communities,\nor subscribe to our mailing list (no spam)\nto get the news first.",[15,2130,2131,2132,2137],{},"Finally,\nif you're excited to bring the latest innovations in routing to more users,\ncheck out Ferrostar ",[34,2133,2136],{"href":2134,"rel":2135,"target":50},"https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fferrostar",[49],"on GitHub",".\nWe have an issue tracker and have even marked some good first issues.\n(We'd especially love to have someone who is excited about Jetpack Compose.)\nLet's build the future of mobile navigation together!",[2139,2140,2141],"style",{},"html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}",{"title":215,"searchDepth":261,"depth":261,"links":2143},[2144,2145,2146,2147,2157,2158,2159],{"id":24,"depth":248,"text":25},{"id":55,"depth":248,"text":56},{"id":115,"depth":248,"text":116},{"id":159,"depth":248,"text":160,"children":2148},[2149,2150],{"id":184,"depth":255,"text":185},{"id":546,"depth":255,"text":547,"children":2151},[2152,2153,2154,2155,2156],{"id":560,"depth":261,"text":561},{"id":682,"depth":261,"text":683},{"id":916,"depth":261,"text":917},{"id":1481,"depth":261,"text":1482},{"id":1711,"depth":261,"text":1712},{"id":2050,"depth":248,"text":2051},{"id":2079,"depth":248,"text":2080},{"id":2116,"depth":248,"text":2117},"The first in a series of technical blog posts covering cross-platform mobile development in Rust.","md",[2163,2164,2165,2166,2167],"Rust","Mobile Development","Navigation SDK","Cross-Platform Development","Ferrostar",{"updated":2169},"2024-02-06","\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-1","Expert","2023-11-15","---\ndescription: \"The first in a series of technical blog posts covering cross-platform mobile development in Rust.\"\npublished: \"2023-11-15\"\nupdated: \"2024-02-06\"\nkeywords:\n  - \"Rust\"\n  - \"Mobile Development\"\n  - \"Navigation SDK\"\n  - \"Cross-Platform Development\"\n  - \"Ferrostar\"\nschemaType: TechArticle\nproficiencyLevel: Expert\n---\n\n# Ferrostar: Building a Cross-Platform Navigation SDK in Rust (Part 1)\n\nThis is the first in a series of technical blog posts\ncovering the joys and challenges\nof building a cross-platform shared library,\nwith a focus on use in mobile applications.\n\nAs a motivating example to frame things,\nwe'll follow the journey of building Ferrostar,\na free and open-source navigation SDK.\nWe want to offer a modern SDK that is cross-platform,\nvendor-neutral, and easily extensible\nboth by direct contributors and developers using it in their apps.\n\n## Why?\n\nBefore we get into the technical weeds too much,\nit's worth taking a moment to ask _why_.\nAt Stadia Maps,\nwe have offered [highly customizable routing](\u002Fproducts\u002Frouting-navigation\u002F)\nsince 2017,\nand support use cases including safe pedestrian routing through lit areas,\ngolf cart routing that prefers multi-use paths,\nand more!\nBut developers are on their own\nwhen it comes to offering a turn-by-turn navigation UI.\nSeveral open-source apps already offer navigation,\nbut the solution is not generalized.\nAmong open-source navigation SDKs,\nMapLibre Navigation Android being the most mature at the time of this writing.\nBut as the name suggests, it is Android-specific.\nAnd since the internals originally came from old open-source Mapbox code,\nit takes considerable effort to use with other vendors,\nand extensibility was never a core design consideration.\n\nStarting a new navigation SDK is certainly an ambitious project,\nbut at the moment there is no navigation SDK that is\nhigh-quality, cross-platform, and open-source.\nWe're here to change that,\nand enable developers to bring these routing innovations to their users.\n\nThis post series is about the nuts and bolts of how we plan to pull it off.\nWe'll start with an overview of ways to share common logic across platforms,\nexplain the architecture we settled on,\nand finish with a deep dive into the first technical topic:\nbinding generation with [UniFFI](https:\u002F\u002Fmozilla.github.io\u002Funiffi-rs\u002F).\n\n## How to Share Common Logic?\n\nWe want to target multiple platforms: iOS and Android for starters.\nCertain code is the same regardless of where you're running it.\nAlgorithms like detecting if the user has strayed off the route\nand calculating the distance to the next turn\nshould be written once and shared across platforms.\nAnd at an even higher level,\nthe broad \"business logic\" of a turn-by-turn navigation experience\nshould be common shared code.\n\nBroadly speaking, there are two common approaches\nfor sharing code across platforms in mobile apps today.\nFirst, there are cross-platform app development frameworks like\nFlutter, React Native, or Kotlin Multiplatform.\nThey promise the ability to write your business logic and UI once\nand run it anywhere.\nThe second approach is to put your business logic in a shared library.\nYou write your code in a reasonably portable language (usually C++)\nand link this with your (platform-specific) applications.\n\nIn general,\nthe cross-platform frameworks are optimized for building *applications*,\nnot *libraries*.\nWe want to ensure that we can support additional platforms\nwith relative ease.\nYou can't just pick up React Native, for example,\nand run it on a bicycle computer.\nThis is situation is improving though.\nFor example, Flutter now runs on certain embedded devices,\nand Kotlin Multiplatform has a quickly improving support for library targets.\nHowever, these are still very early stage developments,\nand they involve a certain amount of ecosystem lock-in.\n\nGiven our requirements, we opted for the shared library approach.\nHistorically, C++ has been a popular choice for the task.\nIt is portable across a wide range of platforms,\ncan be made to interoperate with almost any other language,\nand usually produces small, well-optimized binaries.\nWe preferred to use something else for a few reasons,\nthe most important being\nthat it is difficult to write safe and correct C++ code,\nespecially as a new contributor.\n\nAt Stadia Maps, we have been shipping Rust in production since 2018.\nOur investment has rewarded us with\nconsistently good performance (Rust is keen on zero-cost abstractions),\nmemory safety,\nand excellent maintainability.\nAnd Rust code tends to have fewer bugs as well!\nRust also has excellent interoperability with other languages\nvia a straightforward FFI using the platform's C ABI,\nin stark contrast to C++ which can be rather complex to interface with.\nRust also has a large and growing list of supported platforms,\nincluding iOS and Android on several architectures.\n\nWe aren't alone in our selection of Rust either.\nWe've seen a lot of exciting development in Rust mobile frameworks\nlike [Crux](https:\u002F\u002Fredbadger.github.io\u002Fcrux\u002F),\n[Dioxus](https:\u002F\u002Fdioxuslabs.com\u002F),\nand [Rinf](https:\u002F\u002Fgithub.com\u002Fcunarist\u002Frinf) over the past year.\nBut it's not just new experimental projects that are adopting Rust.\nWe know that both [Mozilla](https:\u002F\u002Fwiki.mozilla.org\u002FOxidation)\nand [Lyft](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=e2ID05dpJzo)\nare using Rust shared libraries in their mobile apps.\nRust is already being used in production mobile apps today.\n\n## Architecture Overview\n\nZooming out, let's look at the broader architecture we adopted.\nThe core is the place where we want all shared logic to live.\nIn the context of our navigation SDK,\nthis includes parsing API responses,\nfiguring out when to advance navigation to the next step,\ncalculating the distance to the next turn,\nand so on.\n\nShared logic in a \"functional core\" isn't new,\nbut we can go one step further and define our data models too!\nThis lets us keep the navigation logic vendor- and platform-agnostic.\nFor example,\nwe define a common location update type with fields like\ncoordinate, timestamp, and heading.\nThis way the core logic doesn't need to care about the differences between\n`CLLocation` and `android.location.Location`.\n\nWe're building a Rust library that's callable via the C ABI.\nBut if we want any mobile devs to even try our framework,\nwe'll need to expose a nicer Swift\u002FKotlin interface.\nThese are called bindings,\nand they provide a thin layer that hides the messy details\nof going back and forth between Rust and the \"native\" mobile code.\n\nFinally, we have the native mobile library.\nThis is the only thing that most app developers will see,\nand it handles communications with the \"outside world\" (ex: internet and GPS),\nand provides a higher level API that can be a bit more \"opinionated.\"\nWe won't say much more about this layer in the rest of our discussions,\nas many volumes have been written on good API and library design.\n\n**Spoiler to avert anxiety and frantic typing**: the rest of this blog goes deep into\n\"how the sausage is made.\"\nTo save you the tedious work of reproducing the structure for your next library,\nwe've put together a [public template repo](https:\u002F\u002Fgithub.com\u002Fianthetechie\u002Funiffi-starter)\non GitHub which is ready to go with no typing or other fiddling required.\nWith that, let's dive in!\n\n![Ferrostar Architecture Diagram](\u002Fimages\u002Fcontent\u002Fferrostar-arch.png)\n\n## Bindings: Bridging Rust and Platform Code\n\nIf you're familiar with the concept of foreign function interfaces (FFIs),\nyou may remember `extern` declarations,\ncalling convention specifications,\nand having to write a lot of boilerplate.\nBut you know what is really good at generating boilerplate?\nComputers!\nEnter: binding generators.\n\nMozilla has developed a fantastic tool for binding generation:\n[UniFFI](https:\u002F\u002Fmozilla.github.io\u002Funiffi-rs\u002F).\nIn their case, the motivation was to build pieces of Firefox\nwhich could be shared across platforms.\nUniFFI is designed to generate safe bindings that feel idiomatic\nto users of the target language.\nThe [design principles](https:\u002F\u002Fmozilla.github.io\u002Funiffi-rs\u002Finternals\u002Fdesign_principles.html)\nof UniFFI aligned well with our requirements for Ferrostar,\nand we think they tend to be a good fit for most similar mobile use cases.\n\n### Cargo workspace setup\n\nLet's dive in with discussing the cargo workspace setup for Ferrostar.\nThis should be a helpful guide for anyone wanting to create their own\ncross-platform library.\n\nWe chose to structure the Rust portion of our library as a workspace\nwith two member projects: `uniffi-bindgen` and `ferrostar`.\nAt the time of this writing,\nthe ability to run the binary from the `uniffi` crate\nis only available in nightly,\nso we opted for a second `uniffi-bindgen` crate in our workspace\nfor the CLI binary.\nHere's what it looks like on disk:\n\n```\n├── Cargo.lock\n├── Cargo.toml\n├── ferrostar\n│   ├── Cargo.toml\n│   └── src\n│       └── lib.rs\n└── uniffi-bindgen\n    ├── Cargo.toml\n    └── src\n        └── main.rs\n```\n\nScaffolding in place, we need to add UniFFI to our workspace's `Cargo.toml`.\nThis way we can keep the binding generator and library versions in sync.\nAt the time this article was originally published,\nproc macro features were still under active development.\nSince then, they have stabilized somewhat,\nand it might make sense to start using the crates.io releases.\nIf you want the newest features and fixes though,\nit's best to use git dependencies.\nTracking the latest commits can cause pain,\nbut it is pretty rewarding if you want access to the latest improvements,\nwhich do come quite fast.\n\n```toml\n[workspace]\n\nmembers = [\n    \"uniffi-bindgen\",\n    \"ferrostar\",\n]\nresolver = \"2\"\n\n[workspace.dependencies]\nuniffi = \"0.26.1\"\n```\n\nThen, we can add it to the `uniffi-bindgen` and `ferrostar` crates respectively.\nIn the `uniffi-bindgen` crate, we need the `cli` feature.\n\n```toml\n[dependencies]\nuniffi = { workspace = true, features = [\"cli\"] }\n```\n\nThe `main` function in `uniffi-bindgen\u002Fsrc\u002Fmain.rs` is a single line.\nHere's the entire file:\n\n```rust\nfn main() {\n    uniffi::uniffi_bindgen_main()\n}\n```\n\nIn the `ferrostar` crate, we also need `uniffi` as a normal dependency\n*and* build dependency.\nWe also need to configure the library target with a few different\n[`crate_type`s](https:\u002F\u002Fdoc.rust-lang.org\u002Freference\u002Flinkage.html).\n\n* `cdylib` - A dynamic system library. Used on most platforms.\n* `staticlib` - A system library with *all* upstream dependencies included.\n  Required to target iOS.\n* `lib` - A Rust library.\n  This isn't mentioned in the UniFFI documentation as of this writing,\n  but if you want to add integration tests (runnable via `cargo test`)\n  and you don't include a Rust library target,\n  you'll get strange errors.\n\n```toml\n[dependencies]\nuniffi.workspace = true\n\n[build-dependencies]\nuniffi = { workspace = true, features = [\"build\"] }\n\n[lib]\ncrate-type = [\"cdylib\", \"staticlib\", \"lib\"]\n```\n\n### Defining what to export\n\nInternally, UniFFI uses an IDL (Interface Definition Language) to describe\nthe public interface of your crate to foreign code (ex: Swift and Kotlin).\nThis is similar to the process of writing a header file in C\nin that it is a set of definitions,\nbut the definition file is used to generate the foreign language bindings.\n\nThis used to require writing a bunch of boilerplate by hand.\nFortunately, UniFFI (starting from v0.25.0) automates this using procedural macros.\nFor our use case,\nwe were able to replace *all* handwritten UDL with macros,\nand expect this will be the best path for most library authors going forward.\n\n#### Functions\n\nLet's get started by exporting a function from Rust\nwhich generates a parser for routing data coming from our APIs.\nA single macro, `uniffi:export` is all we need to export a top-level function.\n\n```rust\n#[uniffi::export]\nfn create_osrm_response_parser(polyline_precision: u32) -> Arc\u003Cdyn RouteResponseParser> {\n    Arc::new(OsrmResponseParser::new(polyline_precision))\n}\n```\n\nUniFFI macros generate the definitions for us automatically\nbased on the function's type signature.\nMany built-in types like numbers, `String`, and `bool`\n\"just work\" as you expect.\nCollections like `Vec\u003CT>`, `Option\u003CT>` and `HashMap\u003CK, V>` are also implemented\nfor all types that UniFFI knows how to represent over the FFI.\nThey even have special cases for types like `Vec\u003Cu8>`\nwhich idiomatically map to a language-specific byte sequence type\n(ex: `Data` in Swift)!\n\nIf anything you export references a type\nthat UniFFI doesn't know how to convert,\nyou will get a compilation error.\nSo, how do we tell UniFFI about new types?\n\n#### Data models\n\nLet's look at two data models: user locations and route requests.\nWe'll export these using derive macros.\nIf you've used the `serde` crate, this should feel familiar.\n\nUser location is just a struct with some properties like coordinates\nand course over ground.\nThe `uniffi::Record` macro exposes our Rust types idiomatically.\nThey will show up as `struct`s in Swift, and `data class`es in Kotlin.\n\n```rust\n#[derive(uniffi::Record)]\npub struct UserLocation {\n    pub coordinates: GeographicCoordinates,\n    pub horizontal_accuracy: f64,\n    pub course_over_ground: Option\u003CCourseOverGround>,\n    pub timestamp: SystemTime,\n}\n```\n\nRoute requests are a little different.\nThey don't really map to a record type.\nSince we want to build an extensible framework,\nwe need to account for multiple ways of getting a route.\nFor example, making an HTTP request, or computing a route on-device.\n\nThis maps well to an enumeration.\nThe `uniffi::Enum` derive macro exports these like you would expect.\n\n```rust\n#[derive(uniffi::Enum)]\npub enum RouteRequest {\n    HttpPost {\n        url: String,\n        headers: HashMap\u003CString, String>,\n        body: Vec\u003Cu8>,\n    },\n    \u002F\u002F ...\n}\n```\n\n#### Objects\n\nYou can also export more complex objects\nwith local state, methods, and constructors!\n\nOne example of this in Ferrostar is our route adapter.\nIt stores a request generator and response parser\nand exposes a generic interface\nso the caller doesn't need to know about the details.\n\n```rust\n#[derive(uniffi::Object)]\npub struct RouteAdapter {\n    request_generator: Arc\u003Cdyn RouteRequestGenerator>,\n    response_parser: Arc\u003Cdyn RouteResponseParser>,\n}\n\n#[uniffi::export]\nimpl RouteAdapter {\n    #[uniffi::constructor]\n    pub fn new(\n        request_generator: Arc\u003Cdyn RouteRequestGenerator>,\n        response_parser: Arc\u003Cdyn RouteResponseParser>,\n    ) -> Self {\n        Self {\n            request_generator,\n            response_parser,\n        }\n    }\n\n    #[uniffi::constructor]\n    pub fn new_valhalla_http(endpoint_url: String, profile: String) -> Arc\u003CSelf> {\n        let request_generator = create_valhalla_request_generator(endpoint_url, profile);\n        let response_parser = create_osrm_response_parser(6);\n        Self::new(request_generator, response_parser)\n    }\n\n    pub fn generate_request(\n        &self,\n        user_location: UserLocation,\n        waypoints: Vec\u003CGeographicCoordinates>,\n    ) -> Result\u003CRouteRequest, RoutingRequestGenerationError> {\n        self.request_generator\n            .generate_request(user_location, waypoints)\n    }\n\n    pub fn parse_response(\n        &self,\n        response: Vec\u003Cu8>,\n    ) -> Result\u003CVec\u003CRoute>, RoutingResponseParseError> {\n        self.response_parser.parse_response(response)\n    }\n}\n```\n\nThis example is a bit more complicated than the others we've looked at so far.\nFirst, we use the `uniffi::Object` derive macro on the struct.\nIn contrast to `uniffi::Record`,\nthis signals that the type will be passed by reference and may have methods.\nThis will be exposed in Swift or Kotlin as a class.\n\nYou must annotate your constructors with the `uniffi::constructor` macro.\nAt the time of this writing, constructors must return an `Arc\u003CSelf>`.\nThe `new` constructor is special, and is exposed most naturally,\nbut other named constructors are also supported if you mark them as such.\nThey are not currently as idiomatic as the default constructor,\nbut I expect things like exposing a `convenience init` to Swift\nwill be possible in the future.\n\nFinally, types exported with `uniffi::Object` tell the binding generator\nto emit a `protocol` (Swift) or `interface` (Kotlin) definition.\n**Pro tip:** This makes it easier for you to write mock implementations\nfor unit testing!\nOur rule of thumb is to _pass_ the protocol\u002Finterface to your native methods,\nand define your instance variables with protocol\u002Finterface types.\nWhen the time comes to construct one,\nyour business logic will dictate which _concrete_ implementation to construct.\nThis makes your code significantly more extensible,\nand allows you to either use implementations from the Rust core\nor define your own in Swift\u002FKotlin.\n\n#### Errors\n\nResult types in Rust signal errors,\nand UniFFI can expose these idiomatically in the generated bindings.\nWe use this pattern in the above example to signal response parsing errors.\n\n```rust\n#[derive(Debug, thiserror::Error, uniffi::Error)]\npub enum RoutingResponseParseError {\n    #[error(\"Failed to parse route response: {error}.\")]\n    ParseError { error: String },\n    \u002F\u002F ...\n    #[error(\"An unknown error parsing a response was raised in foreign code.\")]\n    UnknownError,\n}\n```\n\nThe `uniffi::Error` derive macro will work with any type\nconforming to `std::Error`.\nWe make extensive use of `thiserror` for simplicity.\nIn Swift and Kotlin code,\nthis definition will be bridged into an enum conforming to `Error`\nand a sealed subclass of `Exception` respectively.\n\nBesides exposing the error types themselves,\nUniFFI also makes the error handling idiomatic.\nLet's return to the response parsing function signature.\n\n```rust\npub fn parse_response(\n    &self,\n    response: Vec\u003Cu8>,\n) -> Result\u003CVec\u003CRoute>, RoutingResponseParseError>\n```\n\nSemantically speaking,\nthis function will either return an ordered list of routes,\nor it will generate an error of type `RoutingResponseParseError`.\nAs such, this is how the generated Swift bindings will look:\n\n```swift\npublic func parseResponse(response: Data) throws -> [Route]\n```\n\nSwift does not explicitly list error types, but uses the `throws` keyword.\nIf you inspect the generated code,\nyou can see that UniFFI does indeed handle error variants,\nmapping them to your error types.\n\n#### Traits\n\nCarrying on the motivating use case of vendor-agnostic routing API integration,\nwe define traits in our crate for response parsing\nand similar things that we want to ensure remain generic.\n\n```rust\n#[uniffi::export(with_foreign)]\npub trait RouteResponseParser: Send + Sync {\n    \u002F\u002F\u002F Parses a raw response from the routing backend into a route.\n    fn parse_response(&self, response: Vec\u003Cu8>) -> Result\u003CVec\u003CRoute>, RoutingResponseParseError>;\n}\n```\n\nYou can export traits with the `uniffi::export` macro,\nand will they show up as protocols\u002Finterfaces in your foreign code.\nEven better, if you use `with_foreign`, you can supply foreign code to Rust!\nIt requires a bit of a dance Rust-side\n(you must use types of the form `Arc\u003Cdyn Trait>` at the time of this writing),\nbut that's a small price to pay for such flexibility.\n\nHere's what it looks like in Swift.\nFirst, the Rust `trait` is translated into a Swift `protocol` automatically\nby the UniFFI binding generator.\nThe method even has `throws`, just like you'd write in a pure Swift project!\n\n```swift\npublic protocol RouteResponseParser: AnyObject {\n    \u002F**\n     * Parses a raw response from the routing backend into a route.\n     *\n     * We use a sequence of octets as a common interchange format.\n     * as this works for all currently conceivable formats (JSON, PBF, etc.).\n     *\u002F\n    func parseResponse(response: Data) throws -> [Route]\n}\n```\n\nNow we can implement it just like any other protocol.\nHere's an example of an implementation that we use in our testing.\nIt's a mock that is initialized with a fixed set of routes for\n\"end-to-end\" tests without hitting a network service.\nIn the real world, you might use `Codable`\nto convert the raw data into a Swift model\nand then map it to Ferrostar `Route`s.\n\n```swift\nclass MockRouteResponseParser: RouteResponseParser {\n    private let routes: [Route]\n\n    init(routes: [Route]) {\n        self.routes = routes\n    }\n\n    func parseResponse(response _: Data) throws -> [Route] {\n        routes\n    }\n}\n```\n\nBy making things like `RouteRequestGenerator` and `RouteResponseParser` traits,\nwe make it possible for anyone to do the following:\n\n1. Connect to a custom routing server (for example, one they are self-hosting).\n2. Parse routes in a different format.\n3. Generate routes on-device (in another library, Swift, Kotlin, etc.).\n\nWe're really excited about how this will enable more innovation by developers,\nsince Ferrostar will be easily adaptable to custom routing backends,\nboth for research and commercial use.\n\n## What works well?\n\nThe APIs UniFFI generates truly feel idiomatic!\nIt's hard to overstate how nice this is.\nIt also goes out of its way to generate protocols\u002Finterfaces,\nwhich make it easy to mock parts for unit testing in native code,\nand even has pleasant surprises\nlike `Vec\u003Cu8>` mapping to idiomatic byte sequence types.\n\nUniFFI encourages good hygiene all the way from Rust to your application code\nvia type safety.\nAny type that appears in your signature must be exported,\nso you get checks from end to end.\nAnd Rust functions that return a `Result` type\nare translated into idiomatic errors and exceptions.\n\nYou can even bridge custom types!\nWe don't have space to go deep into that here,\nbut here's some documentation on [how you can do it](https:\u002F\u002Fmozilla.github.io\u002Funiffi-rs\u002Flatest\u002Ftypes\u002Fcustom_types.html)\nwithout using primitives as a lowest common denominator.\n\nFinally, UniFFI makes it easy to use native code implementations\nof a protocol\u002Finterface with your Rust core!\nThis lets us do things like dependency injection in a type-safe manner\nacross an FFI boundary.\n\n## What are the rough edges?\n\nWhen I gave a talk about this three months ago at the Seoul Rust meetup,\nand I had an entire slide full of challenges.\nAlmost all of them were related to UDL,\nand almost are non-issues when you replace UDL generation with proc macros!\nA few remain though.\n\nFirst, **you'll need to change your habits around struct mutation**.\nSince we are introducing foreign code,\nUniFFI must assume that objects may be mutated from other threads.\nSo, you can't have mutable references to `self` in UniFFI interfaces\nand must rely on interior mutability patterns instead.\nImportantly, your storage needs to be both `Send` and `Sync`,\nwhich means you'll need to turn to things like atomics and mutexes.\n\nUniFFI is also evolving quite rapidly.\nWhile some high-profile crates are fairly stable pre-1.0,\nyou can definitely expect some turbulence with UniFFI.\nThe changelogs are normally quite good,\nbut if you're tracking `main`\nyou can expect breaking changes.\nFortunately, the team are _super_ responsive on GitHub.\nThey're working on it every day, and are extremely responsive as maintainers\n(we submitted a PR to improve some docs around proc macros,\nand it was merged after a round of discussions in less than 24 hours).\n\nThe process of generating bindings for Swift and Kotlin,\nintegrating this into your build tooling,\nand packaging everything into a usable SPM \u002F Maven package\nis also quite complex.\nIn fact, we would even suggest that the best practices around this\nare still being explored.\nThis is a complex topic, and this post is already quite long,\nso we'll save it for the next article.\n\n## Wrap-up\n\nIn summary, while it is still a developing field,\nwe couldn't be more excited about the future of Rust on mobile.\nAs we've shown in this post, a shared core is not just possible;\nit's actually pretty straightforward and the developer experience is great\nthanks to UniFFI.\nWe've put what we've learned into a\n[template repo](https:\u002F\u002Fgithub.com\u002Fianthetechie\u002Funiffi-starter)\nso you can try it too, without spending days figuring out boilerplate.\n\nStay tuned for the next post,\nwhere we'll dive deeper into build processes and packaging.\nYou can follow us on social media,\njoin our Slack or Discord communities,\nor subscribe to our mailing list (no spam)\nto get the news first.\n\nFinally,\nif you're excited to bring the latest innovations in routing to more users,\ncheck out Ferrostar [on GitHub](https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fferrostar).\nWe have an issue tracker and have even marked some good first issues.\n(We'd especially love to have someone who is excited about Jetpack Compose.)\nLet's build the future of mobile navigation together!\n","TechArticle",{"title":5,"description":2160},"blog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-1","yNwBxFXqRcgsCQkfAB5UKiyb1KjlOrw9DsCn_z6-0M0",[2179,2189,2200],{"title":2180,"description":2181,"path":2182,"published":2183,"keywords":2184,"rawbody":2188},"Ferrostar: Building a Cross-Platform Navigation SDK in Rust (Part 2 - iOS Packaging)","A deep dive into building and packaging a cross-platform navigation SDK for iOS.","\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-2","2024-12-03",[2163,2185,2186,2187,2166,2167],"iOS Development","Swift Package","XCFramework","---\ndescription: \"A deep dive into building and packaging a cross-platform navigation SDK for iOS.\"\npublished: \"2024-12-03\"\nkeywords:\n  - Rust\n  - iOS Development\n  - Swift Package\n  - XCFramework\n  - Cross-Platform Development\n  - Ferrostar\nschemaType: TechArticle\nproficiencyLevel: Expert\n---\n\n# Ferrostar: Building a Cross-Platform Navigation SDK in Rust (Part 2 - iOS Packaging)\n\nIt's been a while since our [first deep dive](\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-1)\ninto the tech behind [Ferrostar](https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Fferrostar\u002F?utm_source=marketing_site&utm_campaign=ferrostar_tech_blog_2&utm_medium=blog), our new turn-by-turn navigation SDK.\nAs a recap, our [last post](\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-1) covered why we're writing the core in Rust,\nhow we approached both code and data model sharing,\nand looked at the architecture from a high level.\n\nWriting some code for private use is one thing,\nbut publishing it for others to use can be maddeningly difficult.\nWhen the time came for us to publish Swift Packages,\nwe found something closer to the latter.\n\nIn this post, we'll cover the practical details of how we\ncross-compiled the Rust library for iOS,\npackaged it in an XCFramework,\nand published it as a Swift Package.\nThis is the missing manual we wish we had.\n\n![A screenshot of Ferrostar navigating on an iPhone](\u002Fimages\u002Fcontent\u002Fferrostar-landscape-screenshot.png)\n\n## Swift Packaging\n\nLet's start off by looking at the Swift Package structure.\nWe'll need to add _two_ targets to our `Package.swift`:\na binary target for the static library,\nand a Swift target for the generated bindings.\n\nBinary targets in Swift Package Manager can be tricky,\nand this complexity makes our `Package.swift` file a bit unwieldy from the start.\n\n```swift\nlet binaryTarget: Target\nlet useLocalFramework = false  \u002F\u002F NB: Set this to true when developing locally!\n\nif useLocalFramework {\n    binaryTarget = .binaryTarget(\n        name: \"FerrostarCoreRS\",\n        \u002F\u002F IMPORTANT: Swift packages importing this locally will not be able to\n        \u002F\u002F import Ferrostar core unless you specify this as a relative path!\n        path: \".\u002Fcommon\u002Ftarget\u002Fios\u002Flibferrostar-rs.xcframework\"\n    )\n} else {\n    \u002F\u002F Git stuff which we'll come back to at the end.\n    let releaseTag = \"0.23.0\"\n    let releaseChecksum = \"fa308b519db5424d73d00d60ca03fc18c1dcf2f88704aadce29259d12f2de2b2\"\n    binaryTarget = .binaryTarget(\n        name: \"FerrostarCoreRS\",\n        url:\n        \"https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fferrostar\u002Freleases\u002Fdownload\u002F\\(releaseTag)\u002Flibferrostar-rs.xcframework.zip\",\n        checksum: releaseChecksum\n    )\n}\n```\n\nUnfortunately, we haven't found a way to use the same `Package.swift`\nunmodified for both local development within the \"project\" and for publishing.\n(To be fair, this is an extremely rare feature,\nbut it's worth noting that Cargo workspaces let you do this seamlessly with Rust crates.)\nSo, we need to add some switching logic to our package definition.\n\nThe above is the solution we came up with.\nWe always ensure that `useLocalFramework = false` for the version checked in to git.\nPublished versions will need to point to the released artifact,\nor else we'd require every developer to check out and build the whole project locally.\n\nFor local development and CI builds,\nwhere you may need unreleased changes in the Rust core,\nyou have to override the value to `true`.\nIf anyone knows a cleaner solution to this, let us know!\n\nAnother complicating detail is that you _need_ a checksum for remote binary downloads.\nThis is a safety measure built into the Swift Package Manager\nto prevent supply chain attacks.\nSince the checksum can't be known until build time,\nwe have CI rewrite this line in `Package.swift` when publishing a release.\n(We'll cover this dance later.)\n\nOne target down... the other is fortunately a bit simpler.\nWe've called this `FerrostarCoreFFI` in our project,\nto make the distinction clear between the FFI bindings and the Rust binary.\n\n```swift\n.target(\n    name: \"FerrostarCoreFFI\",\n    dependencies: [.target(name: \"FerrostarCoreRS\")],\n    path: \"apple\u002FSources\u002FUniFFI\"\n)\n```\n\nThis is pure Swift and doesn't have any surprises.\nIt just depends on the binary target,\nand includes a source directory which will contain the generated bindings.\n\nYou'll need to add both the source and binary target to your list of `targets`.\nYou can now reference them by name from any of your other targets,\nand\u002For publish them via products.\n\n## Cross compilation\n\nNow that we've looked at the package structure,\nlet's see what actually goes into the two targets.\n\nWe'll tackle cross compilation first.\nWe're going to compile the Rust code for all the relevant iOS target architectures,\njust like Xcode does for our Swift projects.\n\nCross compilation is quite tricky (maybe even traumatic) in some languages,\nbut Cargo is full of pleasant surprises.\n\n```shell\ncargo build --lib --release --target some-target-triple\n```\n\nThat's it!\nJust specify the [target triple](https:\u002F\u002Fdoc.rust-lang.org\u002Fnightly\u002Frustc\u002Fplatform-support.html)!\nFor iOS, we actually need to compile for _three_ targets:\n\n| Target triple           | Description                    |\n|-------------------------|--------------------------------|\n| `x86_64-apple-ios`      | Simulator for Intel-based Macs |\n| `aarch64-apple-ios-sim` | Simulator for ARM-based Macs   |\n| `aarch64-apple-ios`     | iOS devices                    |\n\nIf you try running this on your machine right now,\nyou may get an error since you need to have the target toolchain installed.\nFortunately, this is easy.\nIf you're using `rustup`, you can add any target with the command\n`rustup target add your-target-triple`.\n\nFor iOS, there are just three targets,\nbut we'll have a LOT more by the time we're done with this series.\nRemembering all of these is a lot of work,\nbut there's a relatively under-appreciated file that can help us out:\n[`rust-toolchain.toml`](https:\u002F\u002Frust-lang.github.io\u002Frustup\u002Foverrides.html#the-toolchain-file).\n\nIf you're using `rustup` to install `cargo`,\nit will automatically install the relevant toolchains for you!\nWe can check this into git and call it a day.\nHere's [the full `rust-toolchain.toml` for Ferrostar](https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fferrostar\u002Fblob\u002Fmain\u002Fcommon\u002Frust-toolchain.toml).\n\nWhile this file is specifically designed for use with `rustup`,\nother build systems often have a way of consuming it too.\nFor example, Nix users could leverage [`fenix.fromToolchainFile`](https:\u002F\u002Fgithub.com\u002Fnix-community\u002Ffenix)\nto get the same effect.\n\n## Generating the FFI Bindings\n\nNow that we have the static library built in its first form,\nwe need an easy way for Swift to interface with it.\nIt's [UniFFI](https:\u002F\u002Fgithub.com\u002Fmozilla\u002Funiffi-rs)'s time to shine!\n\nSince our [first post](\u002Fblog\u002Fferrostar-building-a-cross-platform-navigation-sdk-in-rust-part-1), UniFFI has evolved a `uniffi-bindgen-swift` crate concept\nwith a slightly different CLI.\nNot a lot has changed, but it's worth a quick note in case you're on an older version.\nIf you're starting fresh, this [starter template](https:\u002F\u002Fgithub.com\u002Fianthetechie\u002Funiffi-starter)\nhas everything ready to go with the new setup.\n\nHere's how we generate the bindings:\n\n1. Run `uniffi-bindgen-swift` via `cargo run -p`.\n2. Copy the _generated Swift file_ to the appropriate source directory (the `FerrostarCoreFFI` target's source path in our example).\n3. Copy the _clang module map_ into a framework staging directory.\n\nHere's the relevant portion of our build script,\nwhere `$1` is the name of the library (`ferrostar` in our case).\n\n```zsh\ncargo run -p uniffi-bindgen-swift -- target\u002Faarch64-apple-ios\u002Frelease\u002Flib$1.a target\u002Funiffi-xcframework-staging --swift-sources --headers --modulemap --module-name $1FFI --modulemap-filename module.modulemap\nmv target\u002Funiffi-xcframework-staging\u002F*.swift ..\u002Fapple\u002FSources\u002FUniFFI\u002F\nmv target\u002Funiffi-xcframework-staging\u002Fmodule.modulemap target\u002Funiffi-xcframework-staging\u002Fmodule.modulemap\n```\n\n`uniffi-bindgen-swift` looks at the static library\nand generates everything we need to smoothly interact with it from Swift.\nThis includes enums, protocols, library loading code, etc.;\nthis is where it does the magic that makes the Rust library feel like a native Swift package.\nWe just need to copy everything into the location we said in our `Package.swift`.\n\nNOTE: It is possible to integrate these steps into Xcode.\nHowever, given the relative difficulty of doing this\nand the overall flakiness of the Xcode build process,\nwe opted for a simple, reliable shell script.\nThis does mean you need to manually rebuild when changing Rust files,\nbut that's a small step.\n\n## Building a \"fat\" library (universal binary)\n\nAs a last step before creating the XCFramework,\nwe need to do something a bit funny and fuse two of our three binary targets together.\n\nThe way that XCFramework is designed,\nit expects a single binary for each _platform_, not each _CPU architecture_.\nAnd we have two binaries for the iOS Simulator platform:\none for the newer Apple Silicon and one for Intel.\n\nTo get down to one binary per _platform_ (simulator and device),\nwe'll use an old tool, `lipo` to generate a \"fat\" binary with both architectures\nin a single file.\n\n```zsh\nlipo -create target\u002Fx86_64-apple-ios\u002Frelease\u002Flib$1.a target\u002Faarch64-apple-ios-sim\u002Frelease\u002Flib$1.a -output target\u002Fios-simulator-fat\u002Frelease\u002Flib$1.a\n```\n\n## Generating the XCFramework\n\nNow we finally put all of this together in an XCFramework.\nSome teams actually do this by hand,\nsince it's a relatively simple structure,\nbut we'll stick to Apple's official tooling.\n\n```zsh\nxcodebuild -create-xcframework \\\n    -library target\u002Faarch64-apple-ios\u002Frelease\u002Flib$1.a -headers target\u002Funiffi-xcframework-staging \\\n    -library target\u002Fios-simulator-fat\u002Frelease\u002Flib$1.a -headers target\u002Funiffi-xcframework-staging \\\n    -output target\u002Fios\u002Flib$1-rs.xcframework\n```\n\nThis command combines the two static libraries, header, and module map\ninto a single directory.\nAnd if all went well, we now have a working Swift package that you can test locally!\n\n## Distributing the XCFramework\n\nNow it's time to revisit the git and checksum dance that we glossed over at the start.\n\nHere's our distribution checklist:\n\n1. Zip up the folder.\n2. Compute a checksum for the archive.\n3. Update `Package.swift` with your release tag and checksum.\n\nWe script this in our CI actions like so:\n\n```zsh\nditto -c -k --sequesterRsrc --keepParent target\u002Fios\u002Flib$1-rs.xcframework target\u002Fios\u002Flib$1-rs.xcframework.zip\nchecksum=$(swift package compute-checksum target\u002Fios\u002Flib$1-rs.xcframework.zip)\nversion=$(cargo metadata --format-version 1 | jq -r --arg pkg_name \"$1\" '.packages[] | select(.name==$pkg_name) .version')\nsed -i \"\" -E \"s\u002F(let releaseTag = \\\")[^\\\"]+(\\\")\u002F\\1$version\\2\u002Fg\" ..\u002FPackage.swift\nsed -i \"\" -E \"s\u002F(let releaseChecksum = \\\")[^\\\"]+(\\\")\u002F\\1$checksum\\2\u002Fg\" ..\u002FPackage.swift\n```\n\n`ditto` is an archiving utility found on all macOS systems.\nWe use this to create the ZIP archive.\nThen, we compute the checksum using `swift package compute-checksum`.\nThis utility is included in the Xcode Command-line Tools.\nFor versioning, our repository prefers to keep all platforms in sync,\nand derives all versions from the Rust project using a bit of CLI magic.\nTo update `Package.swift`, we use trusty old `sed` to rewrite the relevant lines in-place.\n\nFor your package to be usable by others,\nyou'll need to host your XCFramework for download somewhere and update `Package.swift`\nwith an archive checksum and git tag.\nWe use GitHub's release artifact hosting since it's easy and free,\nbut you can also self-host the archive.\n\n## Wrap-up\n\nPackaging a binary framework for iOS isn't easy,\nand the best practices have evolved in the last few years.\nWhich is probably why no comprehensive (modern) guide exists for our use case!\nWe hope this helps anyone else shipping binary frameworks on iOS.\nWe've been using this process for about a year and a half for Ferrostar,\nand have automated all the steps with shell scripts and CI workflows.\n\nIf you're curious to try this in your own project,\ncheck out the [UniFFI starter](https:\u002F\u002Fgithub.com\u002Fianthetechie\u002Funiffi-starter),\nwhich includes ready-to-go build scripts and all the rest of the project boilerplate.\nAnd for a \"real-world\" CI pipeline on GitHub Actions,\ncheck out the [iOS Release action for Ferrostar](https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fferrostar\u002Fblob\u002Fmain\u002F.github\u002Fworkflows\u002Fios-release.yml).\n\nIn the next installment, we'll cover the build process and packaging for Android.\nGive us a follow on social media, join our Slack or Discord communities,\nor subscribe to our mailing list to get the news first!\n",{"title":2190,"description":2191,"path":2192,"published":2193,"keywords":2194,"rawbody":2199},"Autocomplete Search for SwiftUI and Jetpack Compose","We're making it easier than ever to add autocomplete search to your apps with customizable views for SwiftUI and Jetpack Compose.","\u002Fblog\u002Fautocomplete-search-for-swiftui-and-jetpack-compose","2024-10-14",[2195,2196,2197,2164,2198],"SwiftUI","Jetpack Compose","Autocomplete","Search UI","---\ndescription: \"We're making it easier than ever to add autocomplete search to your apps with customizable views for SwiftUI and Jetpack Compose.\"\npublished: 2024-10-14\nkeywords:\n  - SwiftUI\n  - Jetpack Compose\n  - Autocomplete\n  - Mobile Development\n  - Search UI\nschemaType: TechArticle\nproficiencyLevel: Beginner\n---\n\n# Autocomplete Search for SwiftUI and Jetpack Compose\n\nIf you've ever tried adding location search to a mobile app,\nyou already know how challenging it can be to get the UI right,\njuggle network data sources, and other headaches.\nToday we're making that easier with composable UI elements\nfor [SwiftUI](https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Fswiftui-autocomplete-search\u002F?utm_source=marketing_site&utm_campaign=composable_autocomplete_launch&content=intro)\nand [Jetpack Compose](https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Fjetpack-compose-autocomplete-search\u002F?utm_source=marketing_site&utm_campaign=composable_autocomplete_launch&content=intro).\n\nThese packages build on top of our existing SDKs for Swift and Kotlin,\nand handle all the API communication for you.\nNo boilerplate.\nWith just a few lines of code, you'll have a UI that \"just works.\"\nAnd once the user selects a result from the list,\nyour code can decide what to do next.\nFor example, you can autofill an address form or start turn-by-turn navigation.\n\n# Keeping it customizable\n\nComposable UI frameworks like SwiftUI and Jetpack Compose\nenable many customizations that are difficult or impossible with other approaches—\nall while keeping things easy to use.\nFor example, if you want to customize the look and feel of the list,\njust pass in your own `View` or `@Composable`, and we'll use that instead!\nAnd of course, you can use any SwiftUI or Jetpack Compose modifiers\nto change how the view is laid out in your app.\n\nWe also provide many customizations for you automatically.\nThe view appearance automatically syncs with your material theme or dark mode preferences,\nso it fits in with the rest of your app.\nAnd we even localize the UI based on device settings,\nso users will see results in their language wherever possible.\n\n![Autocomplete Search Example](\u002Fimages\u002Fcontent\u002Fautocomplete-search-localized.png)\n\n## Putting Results in Context—Privately\n\nLocation is critically important context if you want to provide relevant search results.\nFor example, \"London\" might conjure up images of Big Ben for some,\nbut maybe London, Ontario is a bit more relevant if you're in Toronto.\nThat's why our APIs can optionally focus results around a location.\n\nOur new packages support this too and, just like our APIs,\nlocation information is optional.\nAnd if you're in the EU and concerned about GDPR compliance,\nthe new packages support our [EU endpoints](https:\u002F\u002Fdocs.stadiamaps.com\u002Feu-gdpr-endpoints\u002F?utm_source=marketing_site&utm_campaign=composable_autocomplete_launch){ target=\"_blank\" }\nwith a simple flag.\n\n## Learn More & Next Steps\n\n- Check out our quickstart documentation for [SwiftUI](https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Fswiftui-autocomplete-search\u002F?utm_source=marketing_site&utm_campaign=composable_autocomplete_launch&content=next_steps)\n  and [Jetpack Compose](https:\u002F\u002Fdocs.stadiamaps.com\u002Fsdks\u002Fjetpack-compose-autocomplete-search\u002F?utm_source=marketing_site&utm_campaign=composable_autocomplete_launch&content=next_steps).\n- Find the source code on GitHub ([SwiftUI](https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fswiftui-autocomplete-search){ target=\"_blank\" } and [Jetpack Compose](https:\u002F\u002Fgithub.com\u002Fstadiamaps\u002Fjetpack-compose-autocomplete-search){ target=\"_blank\" }; give it a star!).\n- Follow Stadia Maps on [Mastodon](https:\u002F\u002Fen.osm.town\u002F@stadiamaps), [Twitter](https:\u002F\u002Ftwitter.com\u002F@stadiamaps),\n  or [LinkedIn](https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fstadia-maps\u002F), or sign-up for\n  our [newsletter](https:\u002F\u002Feepurl.com\u002Fgs51fD) to hear about exciting developments first.\n",{"title":2201,"description":2202,"path":2203,"published":2204,"keywords":2205,"rawbody":2213},"Why Basic OpenStreetMap Routing Needs Real-Time Traffic","OpenStreetMap is a world-class road network, but without real-time traffic it's a static dataset. Here's why algorithmic ETAs fall apart in production logistics and how Stadia Maps closes the gap with TomTom-powered routing.","\u002Fblog\u002Fwhy-osm-routing-needs-real-time-traffic","2026-05-12",[2206,2207,2208,2209,2210,2211,2212],"Routing","Navigation","OpenStreetMap","Traffic Data","Matrix Routing","Logistics","TomTom","---\ndescription: >-\n  OpenStreetMap is a world-class road network, but without real-time traffic\n  it's a static dataset. Here's why algorithmic ETAs fall apart in production\n  logistics and how Stadia Maps closes the gap with TomTom-powered routing.\nexcerpt: >-\n  OpenStreetMap is great geography, but without real-time traffic it falls\n  short on ETAs. Stadia Maps closes the gap with TomTom-powered routing.\npublished: \"2026-05-12\"\nkeywords:\n  - Routing\n  - Navigation\n  - OpenStreetMap\n  - Traffic Data\n  - Matrix Routing\n  - Logistics\n  - TomTom\nauthor:\n  name: \"Ian Wagner\"\n  jobTitle: \"Founder & President \u002F COO\"\n  sameAs:\n    - \"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fian-w-wagner\u002F\"\n---\n\n# Why Basic OpenStreetMap Routing Needs Real-Time Traffic\n\n> OpenStreetMap (OSM) provides a world-class geographic foundation, but it remains a static dataset. Without real-time traffic integration, routing engines must rely on algorithmic proxies—like road class and legal speed limits—which often lead to unreliable ETAs and logistics bottlenecks.\n\n## The Problem\n\n[OpenStreetMap (OSM)](https:\u002F\u002Fwww.openstreetmap.org\u002Fabout) is one of the world's leading road maps, but a persistent gap remains between fixed geographic data and a [live navigation experience](\u002Fproducts\u002Frouting-navigation\u002F). Without dedicated traffic data, Estimated Times of Arrival (ETAs) are essentially educated guesses. While OSM is excellent at mapping the world's road network, a static dataset cannot capture the actual driving conditions at this exact moment. In enterprise-grade logistics, the lack of live data is often the first significant technical hurdle.\n\n## The Limits of Algorithmic Guesswork\n\nIn the absence of real-time data, a routing engine must estimate travel speeds based on tags and a few common proxies:\n\n- **Road Class:** Assuming a motorway is always faster than a residential street.\n- **Tagged Speed Limits:** Using the legal maximum as the baseline (when the tag even exists).\n- **Network Density:** Adjusting for urban vs. rural environments.\n- **Time of Day:** Using low-granularity buckets like \"daytime\" and \"nighttime.\"\n\nReal-world data show wild variances compared to these static estimates. Road class is a blunt instrument for predicting speed. Missing speed limit tags in open datasets force routing engines to rely on broad averages, resulting in unreliable ETAs and logistics delays. Rule-based algorithms are also notoriously bad at predicting choke points because open datasets don't account for traffic light timings, congestion near specific exits, or the \"invisible\" friction of a busy intersection.\n\n## The Stadia Maps Difference\n\nTo move from guesswork to precision, we integrated [TomTom's global traffic data](https:\u002F\u002Fwww.tomtom.com\u002Fproducts\u002Ftraffic-apis\u002F) directly into the [Stadia Maps routing engine](https:\u002F\u002Fdocs.stadiamaps.com\u002Frouting\u002F). High-resolution historical profiles and live feeds allow for accurate, real-time routing. We provide this through three key technical pillars:\n\n1. **Global Coverage:** Access to consistent data across more countries than almost any other vendor.\n2. **Rapid Updates:** A traffic latency of approximately two minutes allows our API to suggest alternate routes almost as soon as a wreck occurs.\n3. **Historical Profiles:** Deep granularity forms the backbone of predictive routing. High-resolution historical data enables accurate, time-dependent routing in advance, allowing you to plan a route for Tuesday at 8:00 AM based on what might happen on Tuesdays at 8:00 AM.\n\n## Fleet Intelligence at Scale\n\nFor dispatch, optimization, and fleet operations, [matrix routing](https:\u002F\u002Fdocs.stadiamaps.com\u002Frouting\u002Ftime-distance-matrix\u002F) (calculating the time and distance between many origins and destinations) is the engine's most critical function.\n\nThe Stadia Maps infrastructure supports matrix requests that are significantly larger than most competitors allow on standard plans. By integrating traffic data directly into these large-scale requests, we eliminate the need for developers to split requests into smaller chunks, reducing unnecessary complexity and latency.\n\nDevelopers maintain full agency over their implementation. We provide the fastest route based on live conditions, but the frequency of re-routing remains entirely in your control. Choice of revalidation frequency puts you in charge of the trade-off between real-time accuracy and [scaling costs](\u002Fpricing\u002F), ensuring your bills remain as predictable as your ETAs.\n\n---\n\n[Create a free account](https:\u002F\u002Fclient.stadiamaps.com\u002Fsignup\u002F) to start building with real-time traffic and high-performance routing today. Our [documentation](https:\u002F\u002Fdocs.stadiamaps.com\u002Frouting\u002F) provides everything you need to integrate TomTom-powered precision into your existing OSM workflow.\n",1778676026996]