alshdavid / BorrowScript

TypeScript with a Borrow Checker. Multi-threaded, Tiny binaries. No GC. Easy to write.
1.45k stars 16 forks source link

Const declarations and the mutibility of internal object state #34

Closed Isaac-Leonard closed 1 year ago

Isaac-Leonard commented 3 years ago

I've mentioned it in several issues now and feel like it needs an issue of its own at this point. How should object variables declared with const work? Should they be deeply immutable such that all access to them is read only? If so how should the compiler treat methods on the object that would normally modify internal object state? Alternatively should const only enforce shallow immutability like it currently does with typescript? If so this makes static analysis harder and puts more work on the borrow checker where parts of objects might be readable by anyone but other parts must have write ownership while giving more control to the object. This also brings up the issue of whether immutibility should be core to the language. In the current specifications there's been several references to a String.push method that appears to modify a string in place. This would not be modifying the string objects properties and so could not be controlled by readonly or mut modifiers but would be modifying the raw underlying data of the object so I'd imagine would be hard to control with let VS const. If const made the string immutable then we would need an extra method instead of String.push to get a new version of the string if we want to use another modified copy somewhere else. would it be better if the majority of STL objects preferred complete immutability by design so that both let and const declarations of String variables would have a immutable String.push() method that returned a whole new string? And how should control of underlying data be controlled with let and const declarations or some other alternative?

alshdavid commented 2 years ago

It will be deeply immutable.

These calls are equivalent

const foo = 'Hi'
let bar = 'Hi'
let foo = String::from("hi")
let mut bar = String::from("hi")
alshdavid commented 2 years ago

I am working through this part of the language and taking basically all my cues from Rust here. Let me know your thoughts or if I have missed something important:

In Rust, mutability is defined as a property of the binding and describes an inability to modify a value assigned to that binding.

let foo = String::from("Hello");
let mut bar = foo; // Move the value into a new mutable container

bar.push_str(" World");
println!("{:?}", bar);
// println!("{:?}", foo); <- failure: foo is moved to bar

So any values owned by a mutable vector or struct will be mutable and the same goes for an immutable vector or struct

struct Foo { bar: String }
let f = Foo{ bar: String::from("hello") }

let mut strings: Vec<Foo> = vec![f];
strings[0].bar = String::from("World");

println!("{:?}", strings[0].bar);

To explore this a little bit, the operations here are something like:

declare immutable variable "f" as struct "Foo" with properties "hello"

declare mutable variable "strings" as "Vector" of "Foo"
execute method "strings.push" with arguments (move "f")

assign variable "strings[0].bar" as "World"
execute print with arguments (read variable "strings[0].bar")

BS would follow the same direction, with reduced syntax

const foo = "Hello"
let bar = foo; // Move foo into bar

bar.push(" World")
console.log(read bar)
// console.log(foo) <- failure: foo is moved to bar

So that extends to arrays

class Foo {
  public bar: string

  constructor(bar) {
    this.bar = bar
  }
}

const f = new Foo("Hello")

let strings: Foo[] = [f]
strings[0].bar = "World"

console.log(bar) // read is inferred because of "console.log"s implementation
Isaac-Leonard commented 2 years ago

I personally prefer the typescript method of marking individual properties as readonly. I'm not sure how you'd make that work with raw data, such as in buffers or arrays though. I'm also not sure if it would introduce issues in the type system where as the rust method is at least proven to work well.