27 Mar 2026
Planet Mozilla
Firefox Tooling Announcements: MozPhab 2.9.1 Released
Bugs resolved in Moz-Phab 2.9.1:
- bug 2026194
moz-phab upliftshould not set a reviewerless patch as WIP - bug 2026300 Remove redundant "Figuring out who you are" wait message
Discuss these changes in #engineering-workflow on Slack or #Conduit Matrix.
1 post - 1 participant
27 Mar 2026 7:41pm GMT
Cameron Kaiser: So long, cheesegrater
9To5Mac is reporting that Apple has confirmed the Mac Pro is no longer for sale, and indeed, although it was up yesterday, today it's gone.
And are you surprised? After all, Macs have their own bespoke GPUs now, and RAM is on-die. (Glad I sprang for the 16MB option on my M1 Air - that has greatly lengthened its useful service life.) If Apple isn't shipping computers with DIMM slots anymore, then why would they ship PCIe slots for anything else? It wasn't like there were many options you could put in the last iteration anyway, because it too had a non-upgradeable GPU and fixed RAM. Okay, okay, you could stick a whole bunch of NVMe sticks in it and it had good cooling. Was that worth it?
This marks the end of the venerable tower Macs that we loved in the PowerPC days. The Mac Studio is the new Mac Pro. We were always at war with Eastasia.
27 Mar 2026 3:22am GMT
26 Mar 2026
Planet Mozilla
The Mozilla Blog: Mozilla and Mila announce strategic research partnership to advance open source and sovereign AI capabilities

The future of AI should belong to all of humanity, well beyond a handful of countries or companies. For that to happen, AI needs to be open, trusted, and built in ways that give people, institutions, and nations real choices. That's why, today, Mozilla is announcing a strategic partnership with Mila - Quebec Artificial Intelligence Institute to advance open source and sovereign AI capabilities.
This partnership marks a landmark strategic collaboration for both organizations and Mozilla's first-ever partnership with a major AI research lab. It is designed to grow over time, with an inaugural project that focuses on the intersection of trust and usability, including private memory architectures for AI agents.
Mila brings world-class research depth and a proven track record moving ideas into systems - from fundamental breakthroughs to applied tools and the diffusion of technology. Mozilla brings deep open source experience, a vibrant developer community, and the ecosystem instincts needed to turn research into something that spreads. The partnership is designed to show that open source AI can close the gap between cutting-edge research and real-world impact.
As we saw in the web era, having a robust open source software stack can democratize and accelerate innovation in dramatic ways. The same opportunity exists in AI - across compute, models, data, and developer experience - and much of the stack is already being built in the open. But gaps remain, particularly in the layers that determine whether AI is trustworthy, private, and built for a world with many languages, many cultures, and many legitimate ways of organizing society. If we can close those gaps, open source AI becomes a genuine option for the people and institutions that need it most.
"We are working to build a future where AI development is rooted in openness, privacy, and humanity," said Mark Surman, president of Mozilla. "This partnership is a delivery vehicle for that vision - and for breakthroughs that will help governments, developers, and companies alike. Canada can lead on AI sovereignty; we're joining with Mila to make it happen."
"Canada has what it takes to lead on frontier AI that the world can actually trust: the research depth, the values, and the will to do it differently. The next frontier in AI isn't just capability, it is trustworthiness, and Canada is uniquely positioned to lead on both. This partnership is a concrete step in that direction. Open, trustworthy AI isn't a compromise on ambition. It's the higher bar," said Valérie Pisano, president and CEO of Mila.
Together, Mila and Mozilla will develop the technologies and approaches that reduce dependence on closed systems and create more room for transparency, accountability, and shared innovation. The partnership also lays the groundwork for middle-power cooperation in AI: Open source projects have consistently provided the framework for technical collaboration across geographies and jurisdictions. Both organizations welcome research institutions, developers, and like-minded organizations to help fill the stack.
This is the first of what both organizations intend to be a sustained and growing body of work.
Read more about our Open Source AI Strategy here. Learn more about Mila here.
The post Mozilla and Mila announce strategic research partnership to advance open source and sovereign AI capabilities appeared first on The Mozilla Blog.
26 Mar 2026 4:58pm GMT
The Rust Programming Language Blog: Announcing Rust 1.94.1
The Rust team has published a new point release of Rust, 1.94.1. Rust is a programming language that is empowering everyone to build reliable and efficient software.
If you have a previous version of Rust installed via rustup, getting Rust 1.94.1 is as easy as:
rustup update stable
If you don't have it already, you can get rustup from the appropriate page on our website.
What's in 1.94.1
Rust 1.94.1 resolves three regressions that were introduced in the 1.94.0 release.
- Fix
std::thread::spawnon wasm32-wasip1-threads - Remove new methods added to
std::os::windows::fs::OpenOptionsExtThe new methods were unstable, but the trait itself is not sealed and so cannot be extended with non-default methods. - Clippy: fix ICE in
match_same_arms - Cargo: downgrade curl-sys to 0.4.83 This fixes certificate validation error for some users on some versions of FreeBSD. See this issue for more details.
And a security fix:
- Cargo: Update tar to 0.4.45 This resolves CVE-2026-33055 and CVE-2026-33056. Users of crates.io are not affected. See blog for more details.
Contributors to 1.94.1
Many people came together to create Rust 1.94.1. We couldn't have done it without all of you. Thanks!
26 Mar 2026 12:00am GMT
25 Mar 2026
Planet Mozilla
Hacks.Mozilla.Org: Firefox Developer Edition and Beta: Try out Mozilla’s .rpm package!
In January, we introduced our Nightly package for RPM-based Linux distributions. Today, we are thrilled to announce it is now available for Firefox Beta!
Firefox Beta is great for testing your sites in a version of Firefox that will reach regular users in the coming weeks. If you find any issues, please file them on Bugzilla.
Switching to Mozilla's RPM repository allows Firefox Beta to be installed and updated like any other application, using your favorite package manager. It also provides a number of improvements:
- Better performance thanks to our advanced compiler-based optimizations,
- Updates as fast as possible because the .rpm management is integrated into Firefox's release process,
- Hardened binaries with all security flags enabled during compilation,
- No need to create your own .desktop file.
If you have Mozilla's RPM repository already set up, you can simply install Firefox Beta with your package manager. Otherwise, follow the setup steps below.
If you are on Fedora (41+), or any other distribution using dnf5 as the package manager
sudo dnf config-manager addrepo --id=mozilla --set=baseurl=https://packages.mozilla.org/rpm/firefox --set=gpgkey=https://packages.mozilla.org/rpm/firefox/signing-key.gpg --set=gpgcheck=1 --set=repo_gpgcheck=0
sudo dnf makecache --refresh
sudo dnf install firefox-beta
Note: repo_gpgcheck=0 deactivate the signature of metadata with GPG. However, this is safeguarded instead by HTTPS and package signatures (gpgcheck=1).
If you are on openSUSE or any other distribution using zypper as the package manager
sudo rpm --import https://packages.mozilla.org/rpm/firefox/signing-key.gpg
sudo zypper ar --gpgcheck-allow-unsigned-repo https://packages.mozilla.org/rpm/firefox mozilla
sudo zypper refresh
sudo zypper install firefox-beta
For other RPM based distributions (RHEL, CentOS, Rocky Linux, older Fedora versions)
sudo tee /etc/yum.repos.d/mozilla.repo > /dev/null << EOF
[mozilla]
name=Mozilla Packages
baseurl=https://packages.mozilla.org/rpm/firefox
enabled=1
gpgcheck=1
repo_gpgcheck=0
gpgkey=https://packages.mozilla.org/rpm/firefox/signing-key.gpg
EOF
# For dnf users
sudo dnf makecache --refresh
sudo dnf install firefox-beta
# For zypper users
sudo zypper refresh
sudo zypper install firefox-beta
The firefox-beta package will not conflict with your distribution's Firefox package if you have it installed, you can have both at the same time!
Adding language packs
If your distribution language is set to a supported language, language packs for it should automatically be installed. You can also install them manually with the following command (replace fr with the language code of your choice):
sudo dnf install firefox-beta-l10n-fr
You can list the available languages with the following command:
dnf search firefox-beta-l10n
Don't hesitate to report any problem you encounter to help us make your experience better.
The post Firefox Developer Edition and Beta: Try out Mozilla's .rpm package! appeared first on Mozilla Hacks - the Web developer blog.
25 Mar 2026 4:17pm GMT
This Week In Rust: This Week in Rust 644
Hello and welcome to another issue of This Week in Rust! Rust is a programming language empowering everyone to build reliable and efficient software. This is a weekly summary of its progress and community. Want something mentioned? Tag us at @thisweekinrust.bsky.social on Bluesky or @ThisWeekinRust on mastodon.social, or send us a pull request. Want to get involved? We love contributions.
This Week in Rust is openly developed on GitHub and archives can be viewed at this-week-in-rust.org. If you find any errors in this week's issue, please submit a PR.
Want TWIR in your inbox? Subscribe here.
Updates from Rust Community
Official
Foundation
Newsletters
Project/Tooling Updates
- Ferox - A native PostgreSQL client in Rust
- Introducing dial9: A flight recorder for Tokio
- Zellij 0.44: native Windows support, new Rust APIs, remote sessions
- vigil-rs: A Rust Service Supervisor for Containers with PID 1 handling
- Fyrox 1.0.0
- Edge.js: running Node.js safely in a WebAssembly sandbox with Wasmer and WASIX
- Bookokrat v0.3.8: A terminal EPUB / PDF Book Reader now supports DJVU
- flodl v0.1.5: benchmarking Rust vs PyTorch on 7 models - up to 30% faster with 3-20x tighter variance
- Zero-copy Protobuf and ConnectRPC for Rust
- mtp-rs: pure-Rust MTP library, up to 4x faster than libmtp
- [video] Batty: Supervised Agent Execution for Software Teams - Demo
- indxr v0.2.0: A fast codebase indexer and MCP server for AI coding agents
- halloy 2026.5 - desktop IRC client with IRCv3 capabilities
Observations/Thoughts
- Deadlocking a Tokio Mutex without Holding a Lock
- The Good, the Bad, and the Leaky: jemalloc, bumpalo, and mimalloc in meilisearch
- Maximally minimal view types
- Matching Puzzle Pieces and Disappointing Benchmarks
- What If Traits Carried Values
- An effect notation based on with-clauses and blocks
- Rust threads on the GPU
- Elaborating Rust Traits to Dictionary-Passing Style
Rust Walkthroughs
- ZK snarks for rust developer part 2/8
- Let's see Paul Allen's SIMD CSV parser
- Building an LSP Server with Rust is surprisingly easy and fun
- An Incoherent Rust
- Building pentest devices with Rust and ESP32 microcontrollers
- Rust in Intersec's lib-common, Part 1: Integrating Rust in a C Build System
Crate of the Week
This week's crate is noq, a general purpose implementation of the QUIC transport protocol in pure rust.
Thanks to Brendan O'Brien for the self-suggestion!
Please submit your suggestions and votes for next week!
Calls for Testing
An important step for RFC implementation is for people to experiment with the implementation and give feedback, especially before stabilization.
If you are a feature implementer and would like your RFC to appear in this list, add a call-for-testing label to your RFC along with a comment providing testing instructions and/or guidance on which aspect(s) of the feature need testing.
No calls for testing were issued this week by Rust, Cargo, Rustup or Rust language RFCs.
Let us know if you would like your feature to be tracked as a part of this list.
Call for Participation; projects and speakers
CFP - Projects
Always wanted to contribute to open-source projects but did not know where to start? Every week we highlight some tasks from the Rust community for you to pick and get started!
Some of these tasks may also have mentors available, visit the task page for more information.
If you are a Rust project owner and are looking for contributors, please submit tasks here or through a PR to TWiR or by reaching out on Bluesky or Mastodon!
CFP - Events
Are you a new or experienced speaker looking for a place to share something cool? This section highlights events that are being planned and are accepting submissions to join their event as a speaker.
- EuroRust | CFP open until 2026-04-27 | Barcelona, Spain | 2026-10-14 - 2026-10-17
- NDC Techtown 2026 | CFP open until 2026-05-03 | Kongsberg, Norway | 2026-09-21 - 2026-09-24
If you are an event organizer hoping to expand the reach of your event, please submit a link to the website through a PR to TWiR or by reaching out on Bluesky or Mastodon!
Updates from the Rust Project
433 pull requests were merged in the last week
Compiler
- fix some suggestions of the
for-loops-over-fallibleslint - guard patterns: lowering to THIR
- introduce
#[diagnostic::on_move(message)] - make
par_sliceconsistent with single-threaded execution - privacy: fix type privacy holes in RPITITs
Library
- add APIs for dealing with titlecase
- add
is_disconnectedfunctions to mpsc and mpmc channels - implement
BinaryHeap::as_mut_slice - make
OsString::truncatea no-op whenlen > current_len - optimize 128-bit integer formatting
- optimize
BTreeMap::append()using CursorMut vec::Drain::fill: avoid reference to uninitialized memory- unstable impl of
From<{i64, u64}> for f128
Cargo
- clean: Validate that
target_diris not a file cli: Add support for completing--configargument values withnative-completionscli: complete--configand--colorbefore commandcompile: Make build.warnings ignore non-local deps- fix
symlink_and_directorywhen running in a long target dir name - detect circular publish dependency cycle in workspace publish
- fix fetching non-standard git refspecs on non-github repos
- warn when installing with a non-default toolchain
Clippy
- add
BinaryHeap::pop_if()tomanual_pop_if - fix
collapsible_matchfalse positive when the pat binding is moved or mutated - perf:
manual_is_ascii_check,remove 822 million instructions
Rust-Analyzer
- add
ops::AddAssignimplement for IndentLevel - add applicable on LetExpr for
unwrap_tuple - add applicable on let-else branch for
unwrap_block - add auto trait name for
generate_trait_from_impl - add fixes for
non_exhaustive_letdiagnostic - add mapping to syntax factory constructor methods
- add nested lifetime support for
add_lifetime_to_type - add partial selection for
merge_imports - add wrap multiple attr for
wrap_unwrap_cfg_attr - change
test_nameplaceholder toexecutable_arg - complete block .let in closure expression
- offer
'add_braces'on bin-expr assignment - offer on let-expr for
inline_local_variable - fix asm sym operand parsing for parenthesized expr fragments
- fix indent for
convert_closure_to_fn - fix indent for
trait_impl_redundant_assoc_item - fix not applicable on empty
structforno_such_field - fix other predicate for
replace_is_method_with_if_let_method - fix postfix completion indentation compensation
- fix tuple
structpat expected type - add
ident_patqualifier to fully fn param - don't add a second semicolon after postfix completions
- fill match arms on last comma and empty expr
- fix overlap edit on record to tuple assist uses self
- incorrect flychecks with multiple workspaces
- offer on const like path-expr for
'extract_variable' - replace TODO placeholders in next-solver IrPrint with proper formatting
- implement signature type inference
- improve tmp iterator variable name for
convert_for_to_while_let - migrate
convert_from_to_tryfromassist to SyntaxEditor API - project json compatibility improvements
- project json compatibility improvements
- remove doc comments for
generate_trait_from_impl - remove outdated TODO
- remove the mapping for
expr_underscorefrom the syntax factory constructor - replace direct usage of make with syntax factory and migrate assist to syntaxEditor
- support WhileExpr and ForExpr for
add_label_to_loop - support more runnable kinds in project JSON
Rust Compiler Performance Triage
Lot of mixed results this week. One big regression from #152931 makes the results look pretty negative, but otherwise the week was fairly quiet.
Triage done by @panstromek. Revision range: 5b61449e..6f22f613
Summary:
| (instructions:u) | mean | range | count |
|---|---|---|---|
| Regressions ❌ (primary) |
1.0% | [0.1%, 4.2%] | 27 |
| Regressions ❌ (secondary) |
0.2% | [0.0%, 0.6%] | 36 |
| Improvements ✅ (primary) |
-0.1% | [-0.2%, -0.1%] | 3 |
| Improvements ✅ (secondary) |
-0.3% | [-2.8%, -0.0%] | 14 |
| All ❌✅ (primary) | 0.9% | [-0.2%, 4.2%] | 30 |
1 Regression, 1 Improvement, 4 Mixed; 1 of them in rollups 32 artifact comparisons made in total
Approved RFCs
Changes to Rust follow the Rust RFC (request for comments) process. These are the RFCs that were approved for implementation this week:
Final Comment Period
Every week, the team announces the 'final comment period' for RFCs and key PRs which are reaching a decision. Express your opinions now.
Tracking Issues & PRs
- Tracking Issue for fN::BITS
- Fallback
{float}tof32whenf32: From<{float}>and addimpl From<f16> for f32 - Tracking Issue for tcp_deferaccept
- Tracking Issue for #138068: Add
Result::map_or_defaultandOption::map_or_default - Merge
fabsf16/32/64/128intofabs::<F> - 1.95 beta regression: "malformed feature attribute input"
- Never break between empty parens
- Emit retags in codegen
- Optimize
repr(Rust)enums by omitting tags in more cases involving uninhabited variants. - Proposal for a dedicated test suite for the parallel frontend
- Promote tier 3 riscv32 ESP-IDF targets to tier 2
- Proposal for Adapt Stack Protector for Rust
No Items entered Final Comment Period this week for Rust RFCs, Language Team, Leadership Council or Unsafe Code Guidelines.
Let us know if you would like your PRs, Tracking Issues or RFCs to be tracked as a part of this list.
New and Updated RFCs
Upcoming Events
Rusty Events between 2026-03-25 - 2026-04-22 🦀
Virtual
- 2026-03-25 | Virtual (Girona, ES) | Rust Girona
- 2026-03-26 | Virtual (Berlin, DE) | Rust Berlin
- 2026-03-31 | Virtual (Tel Aviv-yafo, IL) | Code Mavens 🦀 - 🐍 - 🐪
- 2026-04-01 | Virtual (Girona, ES) | Rust Girona
- 2026-04-01 | Virtual (Indianapolis, IN, US) | Indy Rust
- 2026-04-02 | Virtual (Nürnberg, DE) | Rust Nuremberg
- 2026-04-04 | Virtual (Kampala, UG) | Rust Circle Meetup
- 2026-04-09 | Virtual (Berlin, DE) | Rust Berlin
- 2026-04-14 | Virtual (Dallas, TX, US) | Dallas Rust User Meetup
- 2026-04-14 | Virtual (London, GB) | Women in Rust
- 2026-04-15 | Virtual (Vancouver, BC, CA) | Vancouver Rust
- 2026-04-19 | Virtual (Dallas, TX, US) | Dallas Rust User Meetup
- 2026-04-21 | Virtual (Washington, DC, US) | Rust DC
Asia
- 2026-03-28 | Delhi, IN | Rust Delhi
- 2026-04-17 | Bangalore, IN, Rust India
- 2026-04-18 | Bangalore, IN, Rust India
Europe
- 2026-03-25 | Dresden, DE | Rust Dresden
- 2026-03-26 | Copenhagen, DK | Copenhagen Rust Community
- 2026-03-26 | Paris, FR | Rust Paris
- 2026-03-27 | Paris, FR | Rust in Paris
- 2026-03-28 | Stockholm, SE | Stockholm Rust
- 2026-04-01 | Berlin, DE | Rust Berlin
- 2026-04-01 | Edinburgh, GB | Rust and Friends
- 2026-04-01 | Oxford, UK | Oxford ACCU/Rust Meetup.
- 2026-04-02 | London, GB | Rust London User Group
- 2026-04-03 | Edinburgh, GB | Rust and Friends
- 2026-04-07 | Basel, CH | Rust Basel
- 2026-04-09 | Geneva, CH | Rust Meetup Geneva
- 2026-04-09 | Oslo, NO | Rust Oslo
- 2026-04-21 | Leipzig, SN, DE | Rust - Modern Systems Programming in Leipzig
North America
- 2026-03-25 | Austin, TX, US | Rust ATX
- 2026-03-25 | New York, NY, US | Rust NYC
- 2026-03-26 | Atlanta, GA, US | Rust Atlanta
- 2026-03-28 | Boston, MA, US | Boston Rust Meetup
- 2026-04-02 | Mountain View, CA, US | Hacker Dojo
- 2026-04-02 | Saint Louis, MO, US | STL Rust
- 2026-04-04 | Boston, MA, US | Boston Rust Meetup
- 2026-04-09 | San Diego, CA, US | San Diego Rust
- 2026-04-11 | Boston, MA, US | Boston Rust Meetup
- 2026-04-14 | Charlottesville, VA, US | Charlottesville Rust Meetup
- 2026-04-16 | Seattle, WA, US | Seattle Rust User Group
- 2026-04-18 | Boston, MA, US | Boston Rust Meetup
- 2026-04-20 - 2026-04-22 | Portland, OR | Tokio
- 2026-04-21 | San Francisco, CA, US | San Francisco Rust Study Group
- 2026-04-22 | Austin, TX, US | Rust ATX
Oceania
- 2026-03-26 | Melbourne, AU | Rust Melbourne
South America
- 2026-04-11 | Argentina, AR | Oxidar Org
- 2026-04-17 | Rio de Janeiro, BR | Meetups Rust RJ
If you are running a Rust event please add it to the calendar to get it mentioned here. Please remember to add a link to the event too. Email the Rust Community Team for access.
Jobs
Please see the latest Who's Hiring thread on r/rust
Quote of the Week
Code does not become better out of thin air just because you rewrite it in #rustlang.
Despite a third week gone by without a suggestion, llogiq is unrelenting in his quest to find a quote worth your while.
Please submit quotes and vote for next week!
This Week in Rust is edited by:
- nellshamrell
- llogiq
- ericseppanen
- extrawurst
- U007D
- mariannegoldin
- bdillo
- opeolluwa
- bnchi
- KannanPalani57
- tzilist
Email list hosting is sponsored by The Rust Foundation
25 Mar 2026 4:00am GMT
Jonathan Almeida: Use Android Studio for resolving conflicts in Jujutsu
You can use JJ's built-in editor for conflict resolutions, but I've found it difficult to follow. A recommendation from co-workers was to use Meld and that has worked quite well once I (begrudingly) accepted that I needed to download another single-purpose app.
Today, another co-worker Andrey Zinovyev found out that we can use Android Studio's (IntelliJ IDEA's really) built-in merge tool to resolve the three-way merge. This is more convenient for me since I spend most of my time here already, so using it as a general purpose merge editor for my work projects is quite nice.
[ui]
merge-editor = "studio"
[merge-tools.studio]
merge-args = ["merge", "$left", "$right", "$base", "$output"]
program = "/Users/jalmeida/Applications/Android Studio Nightly.app/Contents/MacOS/studio"
Presto!

25 Mar 2026 12:00am GMT
24 Mar 2026
Planet Mozilla
The Mozilla Blog: A free VPN you can trust, now built into Firefox

Today we're introducing a free built-in VPN in Firefox, a new IP-protection feature designed to keep you even more private while you browse. We're starting by offering an industry-leading 50 gigabytes of free VPN-browsing each month.
Firefox has long focused on building privacy tools directly into the browser to protect you online. Over the years, we've introduced world-class protections that block known trackers, reduce fingerprinting and limit how companies can follow people across the web. Our goal has been consistent: make meaningful privacy protections accessible to Firefox users every day.
Firefox is the only major browser to include a built-in VPN like this for free - giving you more control over your privacy, right where you browse.
Privacy built into the browser
Every time you visit a website, your IP address is shared automatically. IP addresses help websites know where to send information back to your device, but they can also be used to approximate your location, link your browsing activity across sites and keep logs about your online behavior, meaning websites can track your behavior. It's one of many ways companies track activity across the internet.
Additionally, when you're using public Wi-Fi while at a coffee shop, in a hotel, or in your dorm, people can spy on your network traffic and see which websites you might be visiting.
At Mozilla, we believe people should have stronger protections against this kind of tracking and spying, and that those protections should be easy to use.
Introducing built-in VPN
Our free built-in VPN is designed to make IP protection simple to use in Firefox.
The built-in VPN includes an unprecedented 50 GB per month of free VPN browsing, enough to cover everyday activities like shopping, banking, and reading.
Turn it on in Firefox with a single click. No extra apps. No downloads. Once it's on, Firefox routes your browsing traffic through a proxy network that replaces your IP address before it reaches a website. The sites you visit see the proxy's IP address rather than your own. Firefox already encrypts your traffic with HTTPS, but masking your IP adds another layer of privacy. You can mask the URLs you're visiting from anyone trying to spy on your network traffic on public Wi-Fi, like while you're enjoying a latte at your favorite coffee shop.
If you reach the monthly limit, IP protection is paused until the next cycle. Firefox will require you to confirm before proceeding without the VPN so your browsing doesn't unintentionally continue without IP protection.
Browser-level protection and full-device protection
The free built-in VPN helps secure your traffic while browsing in Firefox, making it a simple way to protect your IP address from being tracked by big tech. However, it does not offer full device protection.
For those looking for broader coverage, you can also choose protection that extends across your entire device, including other apps. The standalone Mozilla VPN subscription offers this capability with unlimited data across multiple devices. Depending on your needs, you can pick the level of privacy and protection that suits you.
We've heard concerns about so-called "free VPNs," which often rely on advertising or selling user data to generate revenue. Firefox's built-in VPN is designed differently. It does not sell your browsing data and does not inject advertising into your traffic. Instead, we offer a limited amount of browser-level protection for free, alongside Mozilla VPN, our paid, unlimited, full-device VPN service.
Read more about the differences between VPNs and web proxies.
Rolling out to Firefox users
The free built-in VPN is currently rolling out as a beta to Firefox desktop users in the United States, the United Kingdom, Germany and France, with plans to expand to additional countries coming soon over the next several releases.
As with many Firefox features, we're introducing it gradually starting in Firefox 149 so we can learn from user feedback and continue improving the experience.
Building a more private web
Protecting privacy online is an ongoing effort. As the web evolves, new technologies create both opportunities and challenges for keeping personal information safe.
Mozilla has spent years building privacy protections - from Total Cookie Protection to Private browsing mode to anti-fingerprinting - directly into Firefox so people have more control over how they experience the web. This built-in VPN is one more way Firefox helps you browse with less exposure and more peace of mind.
By continuing to build these protections into Firefox, we aim to make the web safer, more transparent and more respectful of the people who use it.

Take control of your internet
Download FirefoxThe post A free VPN you can trust, now built into Firefox appeared first on The Mozilla Blog.
24 Mar 2026 4:00pm GMT
Firefox Developer Experience: Firefox WebDriver Newsletter 149
WebDriver is a remote control interface that enables introspection and control of user agents. As such, it can help developers to verify that their websites are working and performing well with all major browsers. The protocol is standardized by the W3C and consists of two separate specifications: WebDriver classic (HTTP) and the new WebDriver BiDi (Bi-Directional).
This newsletter gives an overview of the work we've done as part of the Firefox 149 release cycle.
Contributions
Firefox is an open source project, and we are always happy to receive external code contributions to our WebDriver implementation. We want to give special thanks to everyone who filed issues, bugs and submitted patches.
In Firefox 149, multiple WebDriver bugs were fixed by contributors:
- Sameem updated the screenshot implementations for both the WebDriver BiDi and WebDriver classic protocols to correctly return an error when the requested screenshot area exceeds the maximum supported dimensions, rather than silently clipping it.
- Khalid updated asserts for the WebDriver BiDi network wdspec tests to take a single object for the expected events, wdspec tests to use the "iframe" fixture when inlining iframes, and WebDriver BiDi client modules to validate data types in command responses.
WebDriver code is written in JavaScript, Python, and Rust so any web developer can contribute! Read how to setup the work environment and check the list of mentored issues for Marionette, or the list of mentored JavaScript bugs for WebDriver BiDi. Join our chatroom if you need any help to get started!
General
WebDriver BiDi
- Added support for automatic user prompt handling, which can be configured through capabilities with the
session.newcommand. - Added the
browser.setDownloadBehaviorcommand, which lets clients allow or prohibit the downloads and also set a custom download folder. This behavior can be configured per session or per user contexts. - Added the
script.realmCreatedandscript.realmDestroyedevents for worker realms (for dedicated, shared and service workers). - Fixed an issue where the
browsingContext.userPromptOpenedandbrowsingContext.userPromptClosedevents incorrectly reported the top-level context ID instead of the iframe's context ID on Android. - Fixed the serialization for DOM nodes to stop exposing User Agent specific shadow roots.
- Updated the logic of applying different settings to new browsing contexts to make sure that in the case of creating a browsing context with the
window.opencommand, emulations, viewport overrides, and preload scripts apply before the command returns.
Marionette
- Improved several WebDriver classic commands to handle
implicitandpageLoadtimeouts in line with the script timeout, allowingnullvalues to disable the timeouts.
24 Mar 2026 1:23pm GMT
23 Mar 2026
Planet Mozilla
The Mozilla Blog: Try Tab Notes in Firefox to leave a note on any page

Don't remember why you have all those webpages open? Now you can leave yourself a note for any tab.
Tab Notes - our latest experimental feature in Firefox - are designed to help you remember, reflect, and pick up where you left off on the web by letting you attach a short note to a webpage.
Indicated by a sticky note icon and visible when hovering over tabs, Tab Notes notes remain connected to the page's URL until you delete them. Your notes are yours. They remain private and accessible only to you. Firefox stores them locally in your browser and doesn't send them to Mozilla.
Starting March 24, you can try Tab Notes by following these steps:
- Go to Settings.
- Navigate to Firefox Labs (or enter about:preferences#experimental in the address bar).
- Tick the box beside Tab notes.
Now you're all set! Just right-click or hover over a tab and choose "Add Note" to create your first tab note!
This work is inspired by user research that we conducted last year, which explored how people resume tasks after interruptions. One key insight we learned is that when we are interrupted, even a small reminder or message can significantly improve our ability to resume a task.
Many people use a variety of analog (e.g., sticky notes) and digital tools (e.g., note-taking apps) for these purposes as well, and Tab Notes are our exploration of that idea in a practical, lightweight way. These notes are easy to create, edit, and delete.
This is an early experiment, part of the Firefox Labs program. We are eager for feedback, which you can share on Mozilla Connect or by filing a ticket in Bugzilla.

Take control of your internet
Download FirefoxThe post Try Tab Notes in Firefox to leave a note on any page appeared first on The Mozilla Blog.
23 Mar 2026 7:00pm GMT
The Mozilla Blog: Split View in Firefox: Two tabs side by side, right where you need them

Much of what we do on the web involves looking at more than one thing at a time - booking tickets while checking your calendar, taking notes as you go through a report, or comparing options before making a purchase.
The web is inherently multidimensional. For years, browsing this way meant bouncing back and forth between multiple open tabs, or spinning up multiple windows and using other tools to organize them side-by-side.
The new Split View feature makes these moments easier. It lets you place two tabs next to each other in the same Firefox window so you can see both at once and keep the context you need right in front of you.
Split View is available to all Firefox users starting with Firefox 149, rolling out on March 24. If you'd like to give it a go:
- Make sure you've got the latest version of Firefox.
- Right-click a tab and choose Add Split View. You can also select two tabs, right-click, and choose Open in Split View.
How the Firefox team uses Split View
The team behind Split View has been using it actively over the past few months, and a few workflows quickly stood out. Here are some of the ways people on our team have been using it:
Planning and comparing
Sometimes, you just need two things visible at once.
Gabriel: I've been using Split View to plan camping trips. I open a map on one side and a campsite booking page on the other. This makes it easy to explore locations and check availability without constantly switching tabs.
Everyday tasks
Split View is also helpful for small administrative tasks, the kind that involve copying information from one place to another.
Jonathan: I used Split View while filing my taxes. All my documents - W-2s and other forms - were online, so I kept them open on one side while filling things out on the FreeTaxUSA site on the other. Having both visible made the process much easier.
Note-taking
Ania: I often use Split View when reading and writing at the same time. I'll keep a PDF or article open on one side and take notes on the other as I go. Recently, I've been using this setup while preparing notes for my reading group. It helps me stay focused and quickly organize what I want to share.

What's next for Split View
We built Split View to support the way people naturally move through information on the web - comparing, referencing and writing along the way. This first version focuses on making the most common side-by-side workflows easy.
If you try it, we'd love your feedback on how it fits into your day-to-day browsing and what would make it even more useful.

Take control of your internet
Download FirefoxThe post Split View in Firefox: Two tabs side by side, right where you need them appeared first on The Mozilla Blog.
23 Mar 2026 7:00pm GMT
Mozilla Privacy Blog: Competition, Innovation, and the Future of the Web – Why Independent Browser Engines Matter
Gecko matters because it ensures there's an independent voice shaping how the internet evolves. Without Gecko, the landscape would be dominated by Apple and Google alone.
From accessing information, communicating with others, shopping, working, learning, and entertainment, the vast majority of our time online is spent within a browser. While there are many browsers out there, there are only a few browser engines, the technology necessary to render the data that makes up the web as websites we can use.
Browser engines are among the most complex and consequential pieces of infrastructure on the modern internet. They determine how web standards are implemented, how security and privacy protections are enforced, and which actors ultimately shape the evolution of the web.
As the internet increasingly fragments into walled gardens, and as new technologies like artificial intelligence (AI) are integrated directly into browsers, the influence of browser engines is only growing. When innovation is built on a single dominant engine, it concentrates technical and economic power, narrows choice, and risks steering the web toward the priorities of a few large platforms rather than the public interest.
Gecko is Mozilla's browser engine that powers Firefox. It is one of only three widely used engines and the only independent browser engine. In other words, it is not governed by a company that also runs an operating system to distribute their own browser.
Why Browser Engines Matter
Browser engines (not to be confused with search engines) are the lesser-known technology powering your web browsers.
As the core software layer responsible for interpreting and rendering web content, browser engines play the fundamental role of turning HTML, CSS, and JavaScript into webpages users can interact with.
While browsers are user-facing products, engines are the layer where structural decisions about the web are made. Examples include privacy and security protections, performance characteristics, and the support of APIs. Browser engines are at the heart of the web.
Gecko and the Browser Monoculture
The browser engine landscape is highly concentrated. In 2013, there were five major browser engines. In 2026, there are only three left: Apple's WebKit (which companies are required to use to build on iOS), Google's Blink, and Mozilla's Gecko. Gecko is the only remaining independent browser engine and it powers Firefox.
When engine diversity declines, so does the practical ability to challenge dominant business models or introduce alternative implementations that can put users first through security, privacy, or other features.
There are only three major browser engines left - Apple's WebKit, Google's Blink and Gecko from Mozilla. Apple's WebKit mainly runs on Apple devices, making Gecko the only cross-platform challenger to Blink.
This concentration increasingly risks hard-coding a single company's technical assumptions into the future of the web. Market pressures often turn standards-compliant but differing implementation choices into "bugs" that need fixing.
As both human and AI-driven browsing expand in use, choices about API implementation, data access, and security boundaries at the browser engine level become even more critical. A monoculture at the engine layer could extend to producing a monoculture in AI browsing experiences as well.
Maintaining an Independent Browser Engine Allows Mozilla to be More User-centric
Gecko, as an independent browser engine, tangibly allows Mozilla to build and operate in a way that is aligned with our mission: keeping the web open, secure, privacy-first, and accessible to everyone. It ensures that Mozilla is not only advocating for these principles but actively building the underlying infrastructure that makes them possible.
Through Gecko, we have the freedom to design and ship features based on what is best for users, rather than what is easiest or most profitable within another company's technology stack.
In practice, this enables us to:
- Introduce privacy and security protections that go beyond industry defaults, such as strong cross-site tracking protections and anti-fingerprinting measures.
- Experiment with new user interface designs and customization options that give people more control over how they use the web.
- Build features that reflect Mozilla's mission-driven priorities, even when they diverge from dominant commercial models.
If a small number of vertically integrated companies (AI assistants, search, operating systems, ads) completely control browser engines, then competition, transparency, and user choice on the open web will be much harder to achieve. They will have strong incentives to favour their own services, limit interoperability, and steer defaults and standards to their advantage.
Maintaining an independent engine also lowers barriers for others. Newer entrants to the browser space can rely on interoperability as defined in specifications. If they are not building their own engine, building on Gecko can help sustain a more competitive browser ecosystem. Engine diversity at this foundational layer enables innovation, which is shaped by multiple actors and multiple visions, rather than it being dictated by a single dominant platform.
Browser Engine Plurality Ensures Tech is Built For People, Not Shareholders
In an era defined by platform consolidation and AI-driven change, browser engines can't be treated as invisible infrastructure. Independent engines like Gecko provide a structural counterbalance. Browser engine plurality is needed to ensure competition, transparency, and technology built for people, not shareholders.
As governments increasingly focus on security, resilience and sustainable growth, browser engine competition has a central role to play in avoiding single points of vulnerability or failure. Meaningful competition and a focus on open source approaches help ensure that economies are not locked into a single company's infrastructure and that governments, companies, and people retain real choice over where to build and how to optimize for their needs.
Mozilla has long engaged with policymakers and regulators on the importance of competition and openness at the browser and engine layer. As the web and broader technology landscape continue to evolve, especially in the face of AI, we will continue to advance policies that protect engine diversity, promote fair competition, and ensure the web evolves in the public interest.
The post Competition, Innovation, and the Future of the Web - Why Independent Browser Engines Matter appeared first on Open Policy & Advocacy.
23 Mar 2026 3:13pm GMT
22 Mar 2026
Planet Mozilla
Niko Matsakis: Maximally minimal view types, a follow-up
A short post to catalog two interesting suggestions that came in from my previous post, and some other related musings.
Syntax with .
It was suggested to me via email that we could use . to eliminate the syntax ambiguity:
let place = &mut self.{statistics};
Conceivably we could do this for the type, like:
fn method(
mp: &mut MessageProcessor.{statistics},
...
)
and in self position:
fn foo(&mut self.{statistics}) {}
I have to sit with it but…I kinda like it?
I'll use it in the next example to try it on for size.
Coercion for calling public methods that name private types
In my post I said that if you hvae a public method whose self type references private fields, you would not be able to call it from another scope:
mod module {
#[derive(Default)]
pub struct MessageProcessor {
messages: Vec<String>,
statistics: Statistics,
}
pub struct Statistics { .. }
impl MessageProcessor {
pub fn push_message(
&mut self.{messages},
// -------- private field
message: String,
) {}
}
}
pub fn main() {
let mp = MessageProcessor::default();
mp.push_message(format!("Hi"));
// ------------ Error!
}
The error arises from desugaring push_message to a call that references private fields:
MessageProcessor::push_message(
&mut mp.{messages},
// -------- not nameable here
format!("Hi"),
)
I proposed we could lint to avoid this situation.
But an alternative was proposed where we would say that, when we introduce an auto-ref, if the callee references local variables not visible from this point in the program, we just borrow the entire struct rather than borrowing specific fields.
So then we would desugar to:
MessageProcessor::push_message(
&mut mp,
// -- borrow the whole struct
format!("Hi"),
)
If we then say that &mut MessageProcessor is coercable to a &mut MessageProcessor.{messages}, then the call would be legal.
Interestingly, the autoderef loop already considers visibility: if you do a.foo, we will deref until we see a foo field visible to you at the current point.
Oh and a side note, assigning etc
This raises an interesting question I did not discuss. What happens when you write a value of a type like MessageProcessor.{messages}?
For example, what if I do this:
fn swap_fields(
mp1: &mut MessageProcessor.{messages},
mp2: &mut MessageProcessor.{messages},
) {
std::mem::swap(mp1, mp2);
}
What I expect is that this would just swap the selected fields (messages, in this case) and leave the other fields untouched.
The basic idea is that a type MessageProcessor.{messages} indicates that the messages field is initialized and accessible and the other fields must be completely ignored.
Another possible future extension: moved values
This represents another possible future extension. Today if you move out of a field in a struct, then you can no longer work with the value as a whole:
impl MessageProcessor {
fn example(mut self) {
// move from self.statistics
std::mem::drop(self.statistics);
// now I cannot call this method,
// because I can't borrow `self`:
self.push_message(format!("Hi again"));
}
}
But with selective borrowing, we could allow this, and you could even return "partially initialized" values:
impl MessageProcessor {
fn take_statistics(
mut self,
) -> MessageProcessor.{messages} {
std::mem::drop(self.statistics);
self
}
}
That'd be neat.
22 Mar 2026 4:52pm GMT
Jonathan Almeida: Use |mach try --no-push| for a configuration dry run
I wanted to see what the generated try configuration would be for a new preset I made and did this by submitting real try pushes (with empty so they don't execute resources). What I was looking for was "dry run" in the help files, but I recently discovered it to be --no-push.
$ jj try-push --preset fenix --no-push # 'fenix' as an example preset
Artifact builds enabled, pass --no-artifact to disable
Commit message:
Fuzzy (preset: fenix) query='build-apk-fenix-debug&query='signing-apk-fenix-debug&query='build-apk-fenix-android-test-debug&query='signing-apk-fenix-android-test-debug&query='test-apk-fenix-debug&query='ui-test-apk-fenix-arm-debug&query=^source-test 'fenix&query='generate-baseline-profile-firebase-fenix
mach try command: `./mach try --preset fenix --no-push`
Pushed via `mach try fuzzy`
Calculated try_task_config.json:
{
"parameters": {
"optimize_target_tasks": false,
"try_task_config": {
"disable-pgo": true,
"env": {
"TRY_SELECTOR": "fuzzy"
},
"tasks": [
"build-apk-fenix-android-test-debug",
"build-apk-fenix-debug",
"generate-baseline-profile-firebase-fenix",
"source-test-android-detekt-detekt-fenix",
"source-test-android-l10n-lint-l10n-lint-fenix",
"source-test-android-lint-fenix",
"source-test-buildconfig-buildconfig-fenix",
"source-test-ktlint-fenix",
"source-test-mozlint-android-fenix",
"test-apk-fenix-debug",
"ui-test-apk-fenix-arm-debug",
"ui-test-apk-fenix-arm-debug-smoke"
],
"use-artifact-builds": true
}
},
"version": 2
}
Here, jj try-push is my quick alias around ./mach try for personal simplicity with my workflow.
22 Mar 2026 12:00am GMT
Jonathan Almeida: Create new revisions in Jujutsu with multiple heads
It was one of those "ah ha!" moments for me when I finally used it. Chris Krycho covers the concept of megamerges with this diagram:
m --- n
/ \
a -- b -- c -- [merge] -- [wip]
\ /
w --- x
I've found a more realistic example that best relates to my natural workflow: implementing feature (A) benefitted from having the changes of another tooling patch upgrade (B), that lead to discovering and fixing a bug (C).
(B)
m ----- n
/ \ (A)
a -- b --------- [merge] --- y -- z
\ / \ (C)
------------------- ----- [merge] -- w -- x
\ /
-------------------------------
In this case, trying to separate these into distinct streams of work is quite logically, but we also don't need to leave them unlinked so that they can benefit from each other.
This is what my jj log ended up looking like:
@ oppmsuvz jxxxxxxxxxxxx@gmail.com 2026-03-22 00:34:10 firefox@ 05259417
│ Bug xxxxxxx - Simplify the tests
○ ultowtnr jxxxxxxxxxxxx@gmail.com 2026-03-22 00:34:04 100c4cce
│ Bug xxxxxxx - Include private flag in ShareData
○ lorusmuo jxxxxxxxxxxxx@gmail.com 2026-03-21 20:19:30 905b0460
├─╮ (empty) (no description set)
│ ○ sumqskuu jxxxxxxxxxxxx@gmail.com 2026-03-21 04:22:00 92f6028b
│ │ Add a new secret settings fragment
│ ○ oylmprpu jxxxxxxxxxxxx@gmail.com 2026-03-21 04:22:00 18931825
│ │ Create a new feature for receiving and sending commands.
│ ○ xrnnoonu jxxxxxxxxxxxx@gmail.com 2026-03-21 04:21:48 618020c7
╭─┤ (empty) (no description set)
│ ○ rqlyqqzx jxxxxxxxxxxxx@gmail.com 2026-03-19 17:20:20 c9b5323c
│ │ Bug xxxxxxx - Part 2: Create new android gradle module skill
│ ○ txvozpwz jxxxxxxxxxxxx@gmail.com 2026-03-19 17:20:13 cee18510
├─╯ Bug xxxxxxx - Part 1: Add new gradle example module
◆ pwsnmryn vxxxxxxxxxxxx@gmail.com 2026-03-18 13:21:47 main@origin fa20ce29
│ Bug xxxxxxx - Make my feature work for everyone
~
When I need to submit these, [moz-phab][1 has support for specifying revset ranges with moz-phab start_rev end_rev. However, I can also use jj rebase -s <rev> -d main@origin to put out some try pushes to validate they still work separately - so far, no conflicts in this step.
22 Mar 2026 12:00am GMT
21 Mar 2026
Planet Mozilla
Niko Matsakis: Maximally minimal view types
This blog post describes a maximally minimal proposal for view types. It comes out of a converastion at RustNation I had with lcnr and Jack Huey, where we talking about various improvements to the language that are "in the ether", that basically everybody wants to do, and what it would take to get them over the line.
Example: MessageProcessor
Let's start with a simple example. Suppose we have a struct MessageProcessor which gets created with a set of messages. It will process them and, along the way, gather up some simple statistics:
pub struct MessageProcessor {
messages: Vec<String>,
statistics: Statistics,
}
#[non_exhaustive] // Not relevant to the example, just good practice!
pub struct Statistics {
pub message_count: usize,
pub total_bytes: usize,
}
The basic workflow for a message processor is that you
- accumulate messages by
pushing them into theself.messagesvector - drain the accumulate messages and process them
- reuse the backing buffer to push future messages
Accumulating messages
Accumulating messages is easy:
impl MessageProcessor {
pub fn push_message(&mut self, message: String) {
self.messages.push(message);
}
}
Processing a single message
The function to process a single message takes ownership of the message string because it will send it to another thread. Before doing so, it updates the statistics:
impl MessageProcessor {
fn process_message(&mut self, message: String) {
self.statistics.message_count += 1;
self.statistics.total_bytes += message.len();
// ... plus something to send the message somewhere
}
}
Draining the accumulated messages
The final function you need is one that will drain the accumulated messages and process them. Writing this ought to be straightforward, but it isn't:
impl MessageProcessor {
pub fn process_pushed_messages(&mut self) {
for message in self.messages.drain(..) {
self.process_message(message); // <-- ERROR: `self` is borrowed
}
}
}
The problem is that self.messages.drain(..) takes a mutable borrow on self.messages. When you call self.process_message, the compiler assumes you might modify any field, including self.messages. It therefore reports an error. This is logical, but frustrating.
Experienced Rust programmers know a number of workarounds. For example, you could swap the messages field for an empty vector. Or you could invoke self.messages.pop(). Or you could rewrite process_message to be a method on the Statistics type. But all of them are, let's be honest, suboptimal. The code above is really quite reasonable, it would be nice if you could make it work in a straightforward way, without needing to restructure it.
What's needed: a way for the borrow checker to know what fields a method may access
The core problem is that the borrow checker does not know that process_message will only access the statistics field. In this post, I'm going to focus on an explicit, and rather limited, notation, but I'll also talk about how we might extend it in the future.
View types extend struct types with a list of fields
The basic idea of a view type is to extend the grammar of a struct type to optionally include a list of accessible fields:
RustType := StructName<...>
| StructName<...> { .. } // <-- what we are adding
| StructName<...> { (fields),* } // <-- what we are adding
A type like MessageProcessor { statistics } would mean "a MessageProcessor struct where only the statistics field can be accessed". You could also include a .., like MessageProcessor { .. }, which would mean that all fields can be accessed, which is equivalent to today's struct type MessageProcessor.
View types respect privacy
View types would respect privacy, which means you could only write MessageProcessor { messages } in a context where you can name the field messages in the first place.
View types can be named on self arguments and elsewhere
You could use this to define that process_message only needs to access the field statistics:
impl MessageProcessor {
fn process_message(&mut self {statistics}, message: String) {
// ----------------------
// Shorthand for: `self: &mut MessageProcessor {statistics}`
// ... as before ...
}
}
Of course you could use this notation in other arguments as well:
fn silly_example(.., mp: &mut MessageProcessor {statistics}, ..) {
}
Explicit view-limited borrows
We would also extend borrow expressions so that it is possible to specify precisely which fields will be accessible from the borrow:
let messages = &mut some_variable {messages}; // Ambiguous grammar? See below.
When you do this, the borrow checker produces a value of type &mut MessageProcessor {messages}.
Sharp-eyed readers will note that this is ambiguous. The above could be parsed today as a borrow of a struct expression like some_variable { messages } or, more verbosely, some_variable { messages: messages }. I'm not sure what to do about that. I'll note some alternative syntaxes below, but I'll also note that it would be possible for the compiler to parse the AST in an ambiguous fashion and disambiguate later on once name resolution results are known.
We automatically introduce view borrows in an auto-ref
In our example, though, the user never writes the &mut borrow explicitly. It results from the auto-ref added by the compiler as part of the method call:
pub fn process_pushed_messages(&mut self) {
for message in self.messages.drain(..) {
self.process_message(message); // <-- auto-ref occurs here
}
}
The compiler internally rewrites method calls like self.process_message(message) to fully qualified form based on the signature declared in process_message. Today that results in code like this:
MessageProcessor::process_message(&mut *self, message)
But because process_message would now declare &mut self { statistics }, we can instead desugar to a borrow that specifies a field set:
MessageProcessor::process_message(&mut *self { statistics }, message)
The borrow checker would respect views
Integrating views into the borrow checker is fairly trivial. The way the borrow checker works is that, when it sees a borrow expression, it records a "loan" internally that tracks the place that was borrowed, the way it was borrowed (mut, shared), and the lifetime for which it was borrowed. All we have to do is to record, for each borrow using a view, multiple loans instead of a single loan.
For example, if we have &mut self, we would record one mut-loan of self. But if we have &mut self {field1, field2}, we would two mut-loans, one of self.field1 and one of self.field2.
Example: putting it all together
OK, let's put it all together. This was our original example, collected:
pub struct MessageProcessor {
messages: Vec<String>,
statistics: Statistics,
}
#[non_exhaustive]
pub struct Statistics {
pub message_count: usize,
pub total_bytes: usize,
}
impl MessageProcessor {
pub fn push_message(&mut self, message: String) {
self.messages.push(message);
}
pub fn process_pushed_messages(&mut self) {
for message in self.messages.drain(..) {
self.process_message(message); // <-- ERROR: `self` is borrowed
}
}
fn process_message(&mut self, message: String) {
self.statistics.message_count += 1;
self.statistics.total_bytes += message.len();
// ... plus something to send the message somewhere
}
}
Today, process_pushed_messages results in an error:
pub fn process_pushed_messages(&mut self) {
for message in self.messages.drain(..) {
// ------------- borrows `self.messages`
self.process_message(message); // <-- ERROR!
// --------------- borrows `self`
}
}
The error arises from a conflict between two borrows:
self.messages.drain(..)desugars toIterator::drain(&mut self.messages, ..)which, as you can see,mut-borrowsself.messages;- then
self.process_message(..)desugars toMessageProcessor::process_message(&mut self, ..)which, as you can see,mut-borrows all ofself, which overlapsself.messages.
But in the "brave new world", we'll modify the program in one place:
- fn process_message(&mut self, message: String) {
+ fn process_message(&mut self {statistics}, message: String) {
and as a result, the process_pushed_messages function will now borrow check successfully. This is because the two loans are now issued for different places:
- as before,
self.messages.drain(..)desugars toIterator::drain(&mut self.messages, ..)whichmut-borrowsself.messages; - but now,
self.process_message(..)desugars toMessageProcessor::process_message(&mut self {statistics}, ..)whichmut-borrowsself.statistics, which doesn't overlapself.messages.
At runtime, this is still just a pointer
One thing I want to emphasize is that "view types" are a purely static construct and do not change how things are compiled. They simply give the borrow checker more information about what data will be accessed through which references. The process_message method, for example, still takes a single pointer to self.
This is in contrast with the workarounds that exist today. For example, if I were writing the above code, I might well rewrite process_message into an associated fn that takes a &mut Statistics:
impl MessageProcessor {
fn process_message(statistics: &mut Statistics, message: String) {
statistics.message_count += 1;
statistics.total_bytes += message.len();
// ... plus something to send the message somewhere
}
}
This would be annoying, of course, since I'd have to write Self::process_message(&mut self.statistics, ..) instead of self.process_message(), but it would avoid the borrow check error.
Beyond being annoying, it would change the way the code is compiled. Instead of taking a reference to the MessageProcessor it now takes a reference to the Statistics.
In this example, the change from one type to another is harmless, but there are other examples where you need access to mulitple fields, in which case it is less efficient to pass them individually.
Frequently asked questions
How hard would this be to implement?
Honestly, not very hard. I think we could ship it this year if we found a good contributor who wanted to take it on.
What about privacy?
I would require that the fields that appear in view types are 'visible' to the code that is naming them (this includes in view types that are inserted via auto-ref). So the following would be an error:
mod m {
#[derive(Default)]
pub struct MessageProcessor {
messages: Vec<String>,
...
}
impl MessageProcessor {
pub fn process_message(&mut self {messages}, message: String) {
// ----------
// It's *legal* to reference a private field here, but it
// results in a lint, just as it is currently *legal*
// (but linted) for a public method to take an argument of
// private type. The lint is because doing this is effectively
// going to make the method uncallable from outside this module.
self.messages.push(message);
}
}
}
fn main() {
let mut mp = m::MessageProcessor::default();
mp.process_message(format!("Hello, world!"));
// --------------- ERROR: field `messages` is not accessible here
//
// This desugars to:
//
// ```
// MessageProcessor::process_message(
// &mut mp {messages}, // <-- names a private field!
// format!("Hello, world!"),
// )
// ```
//
// which names the private field `messages`. That is an error.
}
Does this mean that view types can't be used in public methods?
More-or-less. You can use them if the view types reference public fields:
#[non_exhaustive]
pub Statistics {
pub message_count: usize,
pub average_bytes: usize,
// ... maybe more fields will be added later ...
}
impl Statistics {
pub fn total_bytes(&self {message_count, average_bytes}) -> usize {
// ----------------------------
// Declare that we only read these two fields.
self.message_count * self.average_bytes
}
}
Won't it be limited that view types more-or-less only work for private methods?
Yes! But it's a good starting point. And my experience is that this problem occurs most often with private helper methods like the one I showed here. It can occur in public contexts, but much more rarely, and in those circumstances it's often more acceptable to refactor the types to better expose the groupings to the user. This doesn't mean I don't want to fix the public case too, it just means it's a good use-case to cut from the MVP. In the future I would address public fields via abstract fields, as I described in the past.
What if I am borrowing the same sets of fields over and over? That sounds repititive!
That's true! It will be! I think in the future I'd like to see some kind of 'ghost' or 'abstract' fields, like I described in my abstract fields blog post. But again, that seems like a "post-MVP" sort of problem to me.
Must we specify the field sets being borrowed explicitly? Can't they be inferred?
In the syntax I described, you have to write &mut place {field1, field2} explicitly. But there are many approaches in the literature to inferring this sort of thing, with row polymorphism perhaps being the most directly applicable. I think we could absolutely introduce this sort of inference, and in fact I'd probably make it the default, so that &mut place always introduces a view type, but it is typically inferred to "all fields" in practice. But that is a non-trivial extension to Rust's inference system, introducing a new kind of inference we don't do today. For the MVP, I think I would just lean on auto-ref covering by far the most common case, and have explicit syntax for the rest.
Man, I have to write the fields that my method uses in the signature? That sucks! It should be automatic!
I get that for many applications, particularly with private methods, writing out the list of fields that will be accessed seems a bit silly: the compiler ought to be able to figure it out.
On the flip side, this is the kind of inter-procedural inference we try to avoid in Rust, for a number of reasons:
- it introduces dependecies between methods which makes inference more difficult (even undecidable, in extreme cases);
- it makes for 'non-local errors' that can be really confusing as a user, where modifying the body of one method causes errors in another (think of the confusion we get around futures and
Send, for example); - it makes the compiler more complex, we would not be able to parallelize as easily (not that we parallelize today, but that work is underway!)
The bottom line for me is one of staging: whatever we do, I think we will want a way to be explicit about exactly what fields are being accessed and where. Therefore, we should add that first. We can add the inference later on.
Why does this need to be added to the borrow checker? Why not desugar?
Another common alternative (and one I considered for a while…) is to add some kind of "desugaring" that passes references to fields instead of a single reference. I don't like this for two reasons. One, I think it's frankly more complex! This is a fairly straightforward change to the borrow checker, but that desugaring would leave code all over the compiler, and it would make diagnostics etc much more complex.
But second, it would require changes to what happens at runtime, and I don't see why that is needed in this example. Passing a single reference feels right to me.
What about the ambiguous grammar? What other syntax options are there?
Oh, right, the ambiguous grammar. To be honest I've not thought too deeply about the syntax. I was trying to have the type Struct { field1, field 2 } reflect struct constructor syntax, since we generally try to make types reflect expressions, but of course that leads to the ambiguity in borrow expressions that causes the problem:
let foo = &mut some_variable { field1 };
// ------------- is this a variable or a field name?
Options I see:
- Make it work. It's not truly ambiguous, but it does require some semantic diambiguation, i.e., in at least some cases, we have to delay resolving this until name resolution can complete. That's unusual for Rust. We do it in some small areas, most notably around the interpretation of a pattern like
None(is it a binding to a variableNoneor an enum variant?). - New syntax for borrows only. We could keep the type syntax but make the borrow syntax different, maybe
&mut {field1} in some_variableor something. Given that you would rarely type the explicit borrow form, that seems good? - Some new syntax altogether. Perhaps we want to try something different, or introduce a keyword everywhere? I'd be curious to hear options there. The current one feels nice to me but it occupies a "crowded syntactic space", so I can see it being confusing to readers who won't be sure how to interpret it.
Conclusion: this is a good MVP, let's ship it!
In short, I don't really see anything blocking us from moving forward here, at least with a lang experiment.
21 Mar 2026 4:37pm GMT





