
I wanted to learn Rust, so I built Glance: an open-source WinUI 3 (.NET 10) + Rust hybrid PDF reader for Windows 11.
Hey everyone,
I've been wanting to learn Rust for a while, and I finally decided to dive in by building a practical project: Glance, a fast, lightweight, and native PDF viewer designed for Windows 11. It's completely open-source (MIT license).
Instead of using a resource-heavy framework like Electron or going pure C#, I went with a hybrid architecture to combine the modern Fluent UI capabilities of .NET with the systems-level speed and safety of Rust.
The Stack & Architecture:
- Frontend: WinUI 3 (.NET 10) leveraging native Mica backdrops, dynamic light/dark theme support, and virtualized scroll grids.
- Backend (Rust): Google's PDFium library accessed through a raw Rust wrapper (
pdfium-render). Rust handles the heavy lifting: document loading, page rendering to bitmaps, and annotation processing. - Bridge (FFI): A custom P/Invoke layer. All unmanaged FFI calls are offloaded to background threads using a custom asynchronous task bridge to keep the WinUI main thread buttery smooth (60fps scrolling).
- Self-Contained MSIX: The installer bundles both the .NET Runtime and Windows App SDK binaries internally, making the app zero-dependency for end-users (no extra runtime prompts).
Key Features Implemented:
- Side-by-side Split View: Compare two PDFs simultaneously with synchronized scrolling.
- Visual Welcome Screen: An Evince-style welcome grid showing cover-page thumbnails rendered on the fly.
- Anotations & Ink: Digital ink drawing for signatures (using a rounded pen pointer), text highlights, and floating sticky notes.
- Local Persistence: Annotations automatically serialize to a local JSON cache on pointer release with a fully-functional Ctrl + Z undo stack.
Lessons Learned (C# ↔ Rust FFI):
Writing FFI code between a garbage-collected runtime like .NET and a memory-safe language like Rust was the most challenging and rewarding part of this project. I had to learn how to:
- Safely marshal complex structures and UTF-8 string pointers across the boundary.
- Implement a
DllImportResolverto locate and load native binaries dynamically. - Manage memory lifetimes carefully—specifically ensuring native Rust allocations (like page and document pointers) are freed correctly on the Rust side (
memory_free) rather than letting the .NET GC touch them.
The code is fully open-source and I would love any feedback on the FFI structure or code design: