NLL was encountered rampantly throughout the development of the Ion system shell, and was also quite common in the distinst Linux distribution installer. This was never a serious issue though, as we were determined, and knew exactly how to dance around the borrow checker to satisfy it.
In most cases, we could avoid the borrow checker by creating temporary values to store state outside of a loop or match, or by manually calling drop
on a value and hoping that the borrow checker would understand that the borrow is no longer happening. There were a few scenarios where the unsafe
keyword and a raw pointer was required to circumvent the rules entirely, or the decision had to be made to aim for a less-ergonomic API.
Ion
Sadly, most of the NLL comments in Ion were removed for some reason. One of the most common NLL issues within the Ion shell were rampant patterns like the following:
return Some(self.parse_parameter(&self.data[start..self.read - 1].trim()));
These had to be changed to this to compile:
let end = self.read - 1;
return Some(self.parse_array(&self.data[start..end].trim()));
Likewise, the same applied to patterns like so:
self.set("?", self.previous_status.to_string());
Which had the same solution.
Other examples in Ion were a bit more... involved. Figuring out how to handle job expansions, pipelines, and recursive expansions were some of the earliest hurdles.
This entire recent-ish PR is mostly about fixing dependency on the NLL feature so that Ion could build with a stable compiler.
Distinst
Here's a few snippets from distinst, which must retain compatibility for Rust 1.24 from here on out -- at least until 2020. With NLL, unsafe can be avoided entirely, but without it, it's required due to some patterns that cause havoc with borrowck.
// TODO: NLL
let disk = disk as *mut T;
if let Some(partition) = unsafe { &mut *disk }.get_file_system_mut() {
// TODO: NLL
let mut found = false;
if let Some(ref ptarget) = partition.target {
if ptarget == target {
found = true;
}
}
if found {
return Some((path, partition));
}
}
for partition in unsafe { &mut *disk }.get_partitions_mut() {
// TODO: NLL
let mut found = false;
if let Some(ref ptarget) = partition.target {
if ptarget == target {
found = true;
}
}
if found {
return Some((path, partition));
}
}
Apparently, because the first match returns a value that is mutably borrowed, the borrow checker refuses to allow the second loop to also perform a mutable borrow, despite the first loop having exited before the second loop began. The inner borrows in these loops also caused issues.
In another section:
let push = match existing_devices
.iter_mut()
.find(|d| d.volume_group == lvm.0)
{
Some(device) => {
device.add_sectors(partition.get_sectors());
false
}
None => true,
};
if push {
existing_devices.push(LogicalDevice::new(
lvm.0.clone(),
lvm.1.clone(),
partition.get_sectors(),
sector_size,
false,
));
}
And another
let mut found = false;
if let Some(ref mut device) = existing_devices
.iter_mut()
.find(|d| d.volume_group.as_str() == vg.as_str())
{
device.add_sectors(partition.get_sectors());
found = true;
}
if !found {
existing_devices.push(LogicalDevice::new(
vg.clone(),
None,
partition.get_sectors(),
sector_size,
true,
));
}