alshdavid / BorrowScript

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

What about lifetimes? #17

Closed hazer-hazer closed 1 year ago

hazer-hazer commented 3 years ago

Rust has a feature "lifetimes" for reference validation. Of course, it is possible to reduce cases when explicit annotations are required and don't have them at all in the language. Anyway, are there any plans for lifetime annotations? If not, how would cases such as "reference field in structure/class" be checked?

UPD: I'm talking about cases like ...<'a> or &'a ....

SuperSonicHub1 commented 3 years ago

I think copying Rust's & syntax makes sense, unless we want to have a more explicit keyword.

On Fri, Oct 1, 2021, 11:27 AM Hazer Hazer @.***> wrote:

Rust has a feature "lifetimes" for reference validation. Of course, it is possible to reduce cases when explicit annotations are required and don't have them at all in the language. Anyway, are there any plans for lifetime annotations? If not, how would cases such as "reference field in structure/class" be checked?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/alshdavid/BorrowScript/issues/17, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGDQCMQOXZYYSSJ3M3CARGLUEXHMXANCNFSM5FE6ENEQ .

alshdavid commented 3 years ago

Good question. It's actually a bit tricky. To start with, all builtin types are references.

let a: number = 0
a.increment() // mutate the reference to a

Let's write a function that compares two numbers and returns the largest

function main() {
  let a = 5
  let b = 10
  let c = largest(a, b)

  console.log(c)
}

function largest(read x: number, read y: number): number {
  if (x > y) {
    return x
  } else {
    return y
  }
}

Somewhat similarly to Rust:

function largest<'a>(read 'a x: number, read 'a y: number): 'a number {}

// or we can use the 'lifeof' keyword
function largest<lifeof A>(read A x: number, read A y: number): A number {} 

I think this makes sense. number (like all builtin types) is a reference to an object instance and not a primitive value like i32.

What we are saying above is:

"Accept read-only references to variables x and y of type number. Return a reference to a number that has the shortest life of the parameters with the A life"

I think works if there are no non-reference value types. I don't like that the generic lifetime param is used differently to other type params.

This would look like:

function main() {
  let a = 5
  let b = 10
  let c = largest(read a, read b)

  console.log(c)
}

function largest<lifeof A>(read A x: number, read A y: number): A number {
  if (x > y) {
    return x
  } else {
    return y
  }
}

Rust looks like:

fn largest<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {
    if x > y {
        x
    } else {
        y
    }
}

fn main() {
  let a = &5;
  let b = &10;
  let c = largest(a, b);

  print!("{}", c);
}
alshdavid commented 3 years ago

Though the return type irks me.

function largest<lifeof A>(read A x: number, read A y: number): A number {}

Specifically

): A number {}

We could have so that a lifeof parameter (A in this case) itself is a type that has a type parameter.

function largest<lifeof A>(read A x: number, read A y: number): A<number> {}
hazer-hazer commented 3 years ago

Hmm... Now it sounds more and more like "Rust with TypeScript-like syntax", which is not really bad, but I'm not sure if it will work. Rustish safety system is fascinating and the idea of static garbage collection is mind-grabbing, however, some features cannot fully live without GC in runtime, especially JavaScript features. I have so many questions about how this idea is possible to be implemented, but it would be better just to wait until you and contributors will make something workable.