Writing Javascript in Rust ... almost

Well, now you remind me...

Back in 1983 I was contracted by Northern Telecom (Later Nortel) in England to do some work on the BIOS code that they had for a PC compatible system they were selling.

Turned out that the hardware and hence the BIOS they were dealing with came from the then unknown company NOKIA in Finland.

Turned out the variable names and comments in all that code were basically in Swedish.

Let's just say that tackling thousands of lines of x86 assembler in Swedish was a challenge.

As such I don't see a few common Greek letters used in equations where they are expected to be seen as an issue.

But now we have the possibility of non-ASCII identities in Rust we can expect to see all kind of Rust crates written in all kind of languages that are not clear to the English-only world.

Welcome diversity !

2 Likes

You had to change const to let, because Rust const and JavaScript const have totally different semantics.

With an additional #![allow(nonstandard_style)], you could've used a mod Math { ... } instead. That way, nobody would accidentally instantiate your zero-sized struct.

2 Likes

Thanks. Using mod Math .. is a buch better idea.

In case anyone is wondering the JS I'm turning to Rust here comes from the "geodesy" JS library:


In Rust the 'geographiclib' crate is likely a better idea though.


1 Like

Well, actually const in JS is not equal to let in Rust:

JS:

Rust:

That is because all objects are wrapped in an implicit Rc<RefCell<T>> in javascript.

Very true.

But in the context of the code I presented it is plenty close enough as we are dealing with numbers not objects.

Rust:

    let number = 2.1;
    number = 3.2;
    //error[E0384]: cannot assign twice to immutable variable `number`

JS:

    const number = 2.1;
    number = 3.2;
    // Attempting to override 'number' which is a constant

You mean like this:

use std::rc::Rc;
use std::cell::RefCell;

#[derive(Debug)]
pub struct MyStruct {
    x: f64,
    y: f64,
}

pub fn mutate_my_struct (s: Rc<RefCell<MyStruct>>) {
    s.borrow_mut().x *= 10.0;
    s.borrow_mut().y *= 100.0;
}

pub fn main() {
    // Not mutable s
    let s: Rc<RefCell<MyStruct>> = Rc::new(RefCell::new(MyStruct{x: 1.0, y: 2.0}));

    // But let's mutate it anyway!
    mutate_my_struct(s.clone());

    println!("{}, {}", s.borrow().x, s.borrow().y);
    // 10, 200 
}

Whoah! Mind blown. What happened there? We have mutated a thing with no sign of a mut anywhere!

Shamefully after a year of writing Rust I had never read the chapter on Rc and RefCell. Until now. Never knew I needed one.

Sorry if that is a crappy example of using Rc<RefCell<>>.

1 Like

Yes, exactly.

This is why you’ll see some commenters here referring to & and &mut as “shared” and “exclusive” references rather than “immutable” and “mutable” references. The actual guarantees are:

  • While an &T reference exists, the T will not be dropped or moved.
  • While an &mut T reference exists, all access to T will go through this reference, and it won’t be dropped or moved.
1 Like

Sounds reasonable.

I think of “immutable”/“mutable” and “shared”/“exclusive” as interchangeable. The one implies the other, or vice versa, in my mind.

But there you are talking about something being dropped or moved. Where did the ability to mutate the actual values at the end of the references, whilst they are alive, come from when there is no mut anywhere?

(moving my edit to a new reply)

Interior mutability is all about providing safe ways to alter data that you haven’t been guaranteed exclusive access to, which must involve some kind of protection against anyone else seeing an intermediate state:

  • Atomics use special CPU instructions to maintain consistency
  • Cell only allows its value to be swapped out, but not inspected
  • RefCell keeps track of accesses and panics if it would reach an unsafe state
  • Mutex and RwLock use advanced synchronization techniques to work cross-thread.

The more pedantic answer to your question is UnsafeCell: It’s a marker for the compiler that its contents might change through an & reference, and all of the safe types above use it internally.

1 Like

That all make sense to me and is as expected.
Oddly enough I have a good sprinkling of Arc<Mutex<>> in the codes I have written in the last year. No probs.

But still I'm not seeing where the permission to change the variables came from.

What if I did not want to allow changing one or more or all of them?

In this case, you don't want to wrap them in Mutex or RefCell, it's that simple.

Yep, a plain Arc<MyStruct> will only allow taking references to MyStruct and nothing will be able to change it. It's because Arc's only allow taking immutable references that you must use a Mutex to allow mutation. The Mutex allows immutable references to mutate the value inside the mutex by lock()-ing it.

So Mutex is where you granted permission for mutating an object that is behind a read-only reference.

As far as I can tell at the moment there has to be a "mut" in there some where. Else nobody could ever write anything into the MyStruct that gets created.

But I did not create that MyStruct directly. I delegated it to RefCell at the bottom of the rabbit hole.

I get the feeling that if I followed that rabbit hole I would find a "mut" eventually?

Yes/No ?

I think the end of the rabbit whole is this UnsafeCell::get function that takes an immutable reference to &self, but returns a mutable pointer. That's fundamentally unsafe and bypasses Rust's normal rules, but this allows other structs like RefCell to achieve interior mutability.

Ha ha!

I don't care how "unsafe" it is under the hood. I trust that whoever wrote it that nothing bad will happen when I use it.

It would be really cool if someone could point to the line of code under all this and say "Look, there, that is the 'mut' you are looking for"

1 Like

Exactly, that's ( kind of ) why unsafe is there, to be hidden behind safe by people who know what they are doing.

All you have to do is click the [src] link on Docs.rs :slight_smile: :

    pub const fn get(&self) -> *mut T {
        // We can just cast the pointer from `UnsafeCell<T>` to `T` because of
        // #[repr(transparent)]. This exploits libstd's special status, there is
        // no guarantee for user code that this will work in future versions of the compiler!
        self as *const UnsafeCell<T> as *const T as *mut T
    }
1 Like

As you were writing that, that is exactly what I was doing.

Sure enough there it is "as *mut T"

Thanks, I can rest now.

Although it boggles my mind how people actually write such things, in the swamp of generics and traits and so on.

1 Like

Well, someone had to do it for the v1 release, otherwise multi-threading would've been in a sad state with absolutely no way to share mutable state between threads, except for spawning (moving data in) and joining (moving data out) threads.