Зашифрованные порталы: как мы создали Swift-приложение, использующее Rust
2 января 2024 г.Portals — это приложение для Mac, созданное на Swift. Он имеет открытый исходный код и использует Ockam библиотеку Rust для частного обмена TCP- или HTTP-сервисами вашего Mac с друзьями через End-to. -Завершить зашифрованные порталы Оккама. Общая служба появится на их локальном хосте!
В этом посте мы рассмотрим, как приложение SwiftUI для macOS взаимодействует с кодом Rust.
:::совет Если вам интересно попробовать Portals для Mac. Вы можете узнать больше об этом в этой статье, а установка используя Homebrew следующим образом:
brew install build-trust/ockam/portals
:::
Вот двухминутное видео работы приложения:
https://www.youtube.com/watch?v=eWSRNfmURJs&embedable=true а>
Быстрый <> Ржавчина
Функционал «Порталы» уже реализован в библиотеке Ockam Rust. Мы поставили перед собой задачу создать отличный интерфейс для macOS.
Наша первая попытка создания приложения была с использованием Tauri. Это имело смысл, поскольку мы хотели использовать библиотеку ржавчины Ockam, и большинству людей в нашей команде комфортно создавать вещи на Rust. Эту первую версию было легко собрать, и она содержала все необходимые нам базовые функции. Однако опыт использования приложения был не очень хорошим. Tauri дал нам лишь минимальный контроль над тем, как отображается меню и что происходит, когда пользователь взаимодействует с меню. Эта версия приложения выглядела так, как будто она принадлежала 10-летней версии macOS, по сравнению с очень простыми в использовании элементами меню, встроенными в macOS Sonoma.
Мы поняли, что для того, чтобы получить желаемый функционал, нам необходимо создать приложение с использованием SwiftUI.
К сожалению, мы не смогли найти готовое решение для интеграции Swift и Rust, которое дало бы нам лучшее из обоих миров; безопасность Rust и богатый опыт SwiftUI, родной для macOS. Еще немного покопавшись, мы поняли, что можем соединить их с помощью C-89. Rust совместим с соглашением о вызовах C, а Swift совместим с Objective-C, который является расширенным набором C-89.
Мы написали структуры данных Rust, которые должны были быть видны Swift дважды. Одна версия идиоматична для Rust и проста в использовании. Другая версия совместима с C и использует указатели и память, выделяемую вручную с помощью malloc. Мы также представили некоторые C-совместимые API, которые используют необработанные указатели в unsafe Rust для преобразования идиоматических структур данных в их C-совместимые версии. Наконец, мы автоматически сгенерировали заголовок C с помощью библиотеки cbindgen.
Что касается Swift, мы могли бы напрямую вызывать API C, но структуры данных C не являются первоклассными элементами в Swift. Это усложняет их идиоматическое использование в коде SwiftUI. Вместо этого мы решили дублировать структуры данных в Swift и конвертировать их между C и Swift. Это может показаться обременительным, но на практике общее состояние меняется не так уж и часто. Возможность быстро создавать компоненты в SwiftUI с использованием таких конструкций, как if let ...
, ForEach
, enum
и т. д. очень полезна и стоит того, чтобы пойти на компромисс. .
Вот пример той же структуры в четырех ее формах:
// Rust idiomatic structure
#[derive(Default, Clone, Debug, Eq, PartialEq)]
pub struct LocalService {
pub name: String,
pub address: String,
pub port: u16,
pub shared_with: Vec<Invitee>,
pub available: bool,
}
// Rust C-compatible structure
#[repr(C)]
pub struct LocalService {
pub(super) name: *const c_char,
pub(super) address: *const c_char,
pub(super) port: u16,
pub(super) shared_with: *const *const Invitee,
pub(super) available: u8,
}
// Generated C header structure
typedef struct C_LocalService {
const char *name;
const char *address;
uint16_t port;
const struct C_Invitee *const *shared_with;
uint8_t available;
} C_LocalService;
// Swift idiomatic structure
class LocalService {
let name: String
@Published var address: String?
@Published var port: UInt16
@Published var sharedWith: [Invitee]
@Published var available: Bool
}
Приложение Swift статически связывается с нашей библиотекой Rust во время компиляции. Поток данных прост: взаимодействия с пользовательским интерфейсом передаются из Swift в Rust как действия путем вызова C API, события изменения отправляются только Rust, а Swift уведомляется с помощью обратных вызовов, которые приводят к обновлениям пользовательского интерфейса.
Большая часть кода в представлениях SwiftUI выглядит так же, как и в любом другом приложении SwiftUI.
VStack(alignment: .leading, spacing: 0) {
Text(service.sourceName).lineLimit(1)
HStack(spacing: 0) {
Image(systemName: "circle.fill")
.font(.system(size: 7))
.foregroundColor( service.enabled ? (service.available ? .green : .red) : .orange)
if !service.enabled {
Text(verbatim: "Not connected")
} else {
if service.available {
Text(verbatim: service.address.unsafelyUnwrapped + ":" + String(service.port))
} else {
Text(verbatim: "Connecting")
}
}
}
...
Если вам интересно узнать больше, ознакомьтесь с кодом ockam_app_lib crate и приложение Portals на Swift. Makefile в папке Swift также является хорошим местом для изучения того, как обстоят дела. построены и связаны друг с другом.
Если вы заинтересованы в участии в порталах для Mac' s Swift или Rust код, мы добавляем новый хорошие первые выпуски каждую неделю и обожаю помогать новым авторам. Присоединяйтесь к нам в дискорде участников.
:::информация Также появляется здесь.
:::
Оригинал