Engineering July 28, 2026 12 min read

Building SnippetCraft with Rust and Swift

“Works in any app” is one of those phrases that looks great on a landing page. It is also a phrase that can hide an unreasonable amount of platform-specific code. Here are five things the architecture taught me.

Rust and Swift architecture behind the SnippetCraft macOS app

In SnippetCraft, you can select text in another application, press a global shortcut, and turn that selection into a reusable snippet. When it works, it feels like one operation.

Internally, it may involve the macOS Accessibility API, a simulated Command-C, the system pasteboard, a Rust monitoring thread, SQLite, an FFI call, and an AppKit overlay.

I built SnippetCraft as a native macOS snippet manager with clipboard history. The core library is written in Rust; it handles snippets, persistence, and pasteboard monitoring. Swift and AppKit handle the macOS interface and the parts that need close integration with the window system.

The split is useful. It also means that many of the interesting problems live exactly where two systems meet.

The clipboard monitor owns its database connection

The clipboard monitor runs on its own Rust thread. It watches the system pasteboard and writes new text entries to SQLite.

The manager itself contains an ordinary rusqlite::Connection:

pub struct ClipboardManager {
    db_connection: Connection,
}

rusqlite::Connection implements Send, but not Sync. It can move to another thread, but it cannot simply sit inside an Arc<Connection> while multiple threads use it concurrently.

I did not solve this by putting a mutex around the connection field.

Instead, the monitoring thread creates its own ClipboardManager and opens its own connection. That instance belongs to the thread for its entire lifetime. Nothing else needs to borrow it or compete for it.

The rest of the Rust code uses a separate manager wrapped as:

Arc<Mutex<ClipboardManager>>

So there are two ownership models in the same application:

It is not an especially sophisticated design, and that is part of why I like it.

SnippetCraft is a desktop application, not a service processing thousands of concurrent requests. Clipboard entries arrive at human speed. A separate connection for a long-running worker is easier to understand than an elaborate attempt to share one connection everywhere.

I originally thought the cleanest architecture would have one universal manager. In practice, giving the thread its own resources produced the cleaner result.

Rust can only protect the Rust side of the FFI boundary

Swift talks to the Rust library through FFI.

From Swift’s point of view, a Rust ClipboardManager is an opaque pointer. Exported functions receive a raw pointer like this:

*mut ClipboardManager

Inside Rust, that pointer eventually has to be dereferenced:

let manager = unsafe { &mut *manager_ptr };

This is where Rust stops knowing the whole story.

Inside the library, the compiler can verify ownership and borrowing. It knows whether a type is Send or Sync. It can reject two mutable references that should not exist at the same time.

Across FFI, it cannot prove that Swift:

That does not mean the current code is necessarily unsafe in practice. It means its safety depends on conventions the compiler cannot see.

The Swift side has to respect the lifetime and threading rules of the Rust object. Those rules are effectively part of the API, even if they are not expressed in the type signature.

This changed the way I think about unsafe. The unsafe block itself is usually small and obvious. The more important part is everything the unsafe block assumes about the outside world.

A future cleanup could make those assumptions more explicit: route calls through a serial queue, synchronize access inside Rust, or expose a more constrained opaque handle API.

Rust provides strong guarantees, but only for the code and relationships it can observe.

My clipboard-loop fix has a three-second blind spot

SnippetCraft monitors the pasteboard, but it also writes to it.

For example, when the user restores an entry from clipboard history, the Rust method set_text_to_clipboard writes that text back to the system pasteboard.

The monitor then sees a pasteboard change. Without extra state, it could treat SnippetCraft’s own write as a new user copy and save the same value again.

The current implementation prevents this with a global flag:

static IS_LOCK_SAVE_TO_CLIPBOARD: OnceLock<Mutex<bool>>;

Before writing to the pasteboard, SnippetCraft enables the flag. While it is active, the monitor skips saving clipboard changes.

A separate thread waits for three seconds and then clears the flag.

It works, but it is deliberately blunt.

If the user copies something else during those three seconds, the monitor may ignore that copy as well. The flag does not identify one specific pasteboard generation; it temporarily disables history recording.

There is another detail that matters. This lock belongs to the Rust path used when restoring an entry from clipboard history. Normal snippet insertion writes to NSPasteboard.general directly from Swift, so it does not automatically pass through the same mechanism.

To the user, both actions put text into another application. Internally, they take different routes.

The better long-term solution is probably to record the pasteboard’s changeCount after an internal write and ignore only that exact generation. A user copy that happens immediately afterward would then remain visible to the monitor.

The three-second lock is one of those solutions that is easy to explain, easy to ship, and slightly uncomfortable once you examine every possible timing case.

I have learned to be suspicious whenever time is being used as a substitute for causality.

Getting selected text requires a fallback that changes the clipboard

One of SnippetCraft’s global shortcuts captures the text currently selected in another application.

The preferred path uses the Accessibility API. SnippetCraft asks the focused accessibility element for its selected text through AXUIElementCopyAttributeValue.

When the active application exposes the expected attribute, this is ideal. The selected text can be read directly without touching the clipboard.

The problem is that not every application exposes it.

Support depends on the application, its UI framework, and sometimes the exact control that currently has focus. One text field may behave correctly while another component in the same application returns nothing useful.

So SnippetCraft has a fallback: simulate Command-C using CGEvent, then read the copied value from the pasteboard.

This is less elegant, but far more applications understand Command-C than expose selected text correctly through Accessibility.

It also means that capturing a selection can modify the clipboard and interact with the clipboard monitor. A feature that looks like “give me the selected text” becomes a coordination problem between several subsystems.

I spent time looking for one macOS API that would work everywhere. I do not think that API exists.

For system-wide utilities, “works everywhere” usually means “try the clean API first, then keep a pragmatic fallback ready.”

The main UI uses SwiftUI; the overlays do not

SnippetCraft’s main interface uses SwiftUI. The global overlays are built differently.

Snippet search, clipboard history, and variable input use borderless NSWindow instances with interfaces written directly in AppKit. Their UI is assembled from controls such as:

There is no NSPanel hosting SwiftUI content in these overlays. Both the window and its contents are AppKit.

The search overlay also takes focus intentionally:

window.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)

This is not an attempt to create a passive window that stays out of the way. SnippetCraft activates because the user needs to start typing immediately, move through results with the arrow keys, press Enter to choose a snippet, and close the window with Escape.

For this interaction, controlling the first responder and keyboard behavior is more important than keeping the application inactive.

I could probably reproduce much of this in SwiftUI with enough bridging and customization. I am not convinced the result would be simpler.

AppKit gives me direct control over the exact pieces that matter for an overlay: the window level, focus, table selection, keyboard events, and activation.

Using SwiftUI for the main application and AppKit for the overlays is not the perfectly uniform architecture I might have designed on paper. It is, however, a practical division of responsibilities.

Where the project stands now

The current architecture is a mix of several worlds:

RustSnippet logic, SQLite persistence, and pasteboard monitoring.
FFIOpaque pointers connecting Swift to the Rust library.
SwiftUIThe main application interface.
AppKitGlobal overlays and lower-level macOS behavior.
AccessibilityThe preferred path for reading selected text.
CGEventThe practical Command-C compatibility fallback.

It is not especially pure. I no longer think purity is the right goal for this kind of application.

A system-wide macOS utility inevitably sits between several APIs and execution models. The useful question is not “How do I hide all these boundaries?” It is “Which layer should be responsible for each boundary?”

That question has produced better decisions than trying to force everything into one framework or one shared object.

Next: figuring out what is actually portable

I am considering a Windows version of SnippetCraft using Tauri.

A good portion of the Rust code should be reusable: the snippet model, persistence, and other platform-independent logic. The operating-system integration is a different story.

Windows has its own APIs and behavior for clipboard monitoring, global shortcuts, active-window detection, input simulation, tray applications, and overlay windows.

The difficult part will not be reusing Rust. The difficult part will be deciding exactly where the portable core ends and platform-specific behavior begins.

I would prefer not to fill one library with if macOS and if Windows branches until nobody knows which assumptions apply where.

That experiment will probably become another article.

Try SnippetCraft on your Mac

Trigger expansion, dynamic variables, global snippet search, documentation, and clipboard history in one native app.

↓ Download for macOS

Free · macOS 13+ · Apple Silicon & Intel

Editorial note: an AI writing assistant helped translate and edit the English version. The implementation details and engineering experience are the author’s own, and every technical claim was checked against the SnippetCraft source code.