I've had a question about Rust from a friend. They are proficient in kotlin, but they don't understand the mutability concepts of rust. What they asked me is this:
"How would you do the following in Rust?"
import kotlin.test.*
fun main() {
val a = listOf(1, 2, 3, 4, 5)
val b = a.filter { it < 3 }
a[0] = 10
assertEquals(
b[0],
10,
"Modifying a results in modifying b, because the elements are references"
)
}
I've programmed in rust for over a year now, and I can't seem to figure it out. The first thing that looked odd to me is that both a and b are declared immutable by the val keyword and thus they cannot be reassigned, but they can be mutated. Anyway, back to my question. How would you do this in Rust? Preferably without using unsafe. I'd imagine how one could use raw pointers to allow modification of list a after referencing it.
Since this would be modifying the initial list from under the iterator (the iterator would be borrowing the list, so there's an immutable borrow), you couldn't do that.
use std::cell::RefCell;
fn main() {
let a = (1..=6).map(RefCell::new).collect::<Vec<_>>();
let b = a.iter().filter(|x| *x.borrow() < 3).collect::<Vec<_>>();
*a[0].borrow_mut() = 10;
println!("{}", b[0].borrow());
}
By using refcell, we tell the compiler that we will take care of the ownership rules ourselves, but it adds an extra layer of complexity.
But, as said before in the thread, that isn't really how you write code in rust... to actually give you a better solution we'd need to know what are you trying to achieve with that code
I get that, references are either a single &mut or nonzero &'s, never both. I've never really looked into interior mutability, but quinedot showed how you would do that and if I ever really need to do this in rust, I would probably approach it that way.
Which is precisely the point. Interior mutability is a hassle, too.
So, basically, the question becomes a bit like “here's the nice collection of footguns: one can be used to shoot yourself in your foot, that one blows up your kneecaps and this one here can destroy the whole leg in one shot… where are they in the Rust?” and the answer to that is pretty natural: they are under lock and key as they should be.
Rust is designed around trying to prevent such long-distance coupling (which is everywhere in modern OOP languages). Surprisingly enough languages like Kotrlin and/or Typescript are trying to do that, too… but they couldn't do that properly because the target audience values convenience much more than safety and correctness.
Yet, unlike languages like Haskell, Rust provides some ways to get these footguns back if you really need them (although model Haskell offers that, too). But these are not easy to use and not convenient to use… but that's fine since you are not supposed to use them unless really forced by some very pressing need).
First of all, yes, in Kotlin you can mutate val bindings, however their interface needs to allow that, and List doesn't, so you get an error saying "No set method providing array access". You need a MutableList for that, which you can get by using mutableListOf instead of listOf.
I would say you can emulate that in Rust, but why not just use a mutable binding in the first place? In Kotlin it works like that due to the way the language handles mutability, and a var binding doesn't make it work, but that's just how that language works.
And lastly, that assertion fails. b is a different list, and assigning an element to a will change only that list. Yes, elements are still references, so by using internal mutability you can observe changes inside elements of a in b, but integers don't have internal mutability.
Totally agree with you. I can't think of a scenario when you need this, they are indeed footguns anyways. I never said I liked this behaviour in the JVM, but at least now I know that it's at least possible to do with safe Rust.
The average Rust programmer doesn't need it and it's considered an advanced topic. I've never touched it and I don't think I will any time soon.
I'm sorry, I tried to change my example into something more concise, without testing. The original code snippet:
import kotlin.test.*
fun main() {
val a = listOf(
Person("John", 33),
Person("Doe", 50)
)
val b = a.filter { it.age < 40 }
a[0].age = 10
assertEquals(
b[0].age,
10,
"Modifying a results in modifying b, because the elements are references"
)
}
data class Person(var name: String, var age: Int)
This only seems to happen to Object's in java, as they are handled as references in the JVM. Because of the var keyword in the Person class those fields can be modified without the need of "mutating" the array.
Ah, for this more complicated example the Cell approach doesn't work because the Person type wouldn't be Copy, which is required for Cell::get.
Using interior mutability in this case would require using RefCells which is more messy than Cell because it requires runtime rather than compile-time checks, and because it doesn't have from_mut and as_slice_of_cells.
This shows a limitation of the interior mutability approach.
Note that this code snippet is the perfect way to discuss why the Kotlin approach is problematic.
Note that nothing prevents you from writing a[0].age = 45 and then ending up with the wrong set of Persons in b.
Rust approach would be to make it impossible to change a while b is alive. This ensures that b wouldn't contain incorrect information. If it contained a list of persons whose age was less than 40 when it was created then all these persons wouldn't suddenly age while we are using that data.
This may not be as convenient as what Kotlin does, but it, sure as hell, is safer… this explains why Rust have that fancy ownership concept and then lifetimes and borrow rules.
Yes but it's a bit risky because the compiler doesn't protect you from accidentally calling borrow_mut twice from two different places which seems innocuous but will result in a panic.
And if you want to have a regular mutable array of Person, you then won't be able to reinterpret it as an array of RefCell<Person> in some local piece of code, whereas for Cell that is fine to do.