M
Modular2mo ago
jmky

Is there a disdain for Rust from the Mojo team?

I read Chris' proposal on naming of provenance. I don't understand why can't we just adopt Rust's convention of using &'a T and &'a mut T instead of going one round of inventing new keywords ref[lifetime] and refmut[lifetime]. Mojo is unique on its own: 1) It does ASAP destructors. 2) It provides APIs to low level MLIR artefacts. There is really, in my humble opinion, NO NEED to be 'different' in areas where it won't make a huge difference. And also, why is there a subtle leaning on implementing features that are more closely associated to C++ (i.e. __copyinit__, a.k.a. copy ctor, etc), whereas there is an aversion to associate itself with Rust? Rust has done many things right, in terms of provenance tracking, quality of life enhancements, ergonomics and beautiful syntaxes. Even a 'safe' version of C++ has no-shame 'copying' Rust features, why should Mojo be ascribed to this aversion?
GitHub
mojo/proposals/lifetimes-keyword-renaming.md at nightly · modularml...
The Mojo Programming Language. Contribute to modularml/mojo development by creating an account on GitHub.
17 Replies
Zbornak
Zbornak2mo ago
Disdain for not adopting another language's conventions?
jmky
jmkyOP2mo ago
Don't you think so? But they have no qualms for __moveinit__, even __takeinit__.
Mohamed Mabrouk
Mohamed Mabrouk2mo ago
Early on, Mojo adapted the & convention for immutable references which was later renamed to borrowed. I think the main idea in Mojo syntax is ensuring clarity of syntax on the expanse of line space. so & and other signs does not really fit in the current Mojo syntax. Conceptually, Mojo explicitly follows Rust's lead in exclusivity checking and lifetime tracking.
Darin Simmons
Darin Simmons2mo ago
Skipping the entire Rust-Mojo thing, names are contracts. If it's called thing over there and then you call it thing over here, thing has already been established. If, for any reason, you want to do something different with thing, you have broken the contract. If you call it new_word, you can adopt as much, as little of it. Put another way, using the same word is equivalency and a new word is similarity.
ksandvik
ksandvik2mo ago
Using the same words as other languages also makes an association that the behavior is the same, and it might not be....
Darin Simmons
Darin Simmons2mo ago
@jmky Chris Lattner has posted a new discussion
GitHub
[Discuss] Resyntaxing argument conventions and References · Issue #...
The design of the Mojo references subsystem is starting to come together. To finalize the major points, it helps to come back and re-evaluate several early decisions in Mojo to make the design more...
Nick!
Nick!2mo ago
@jmky Mojo's "lifetimes" don't actually work the same as Rust. They behave very, very differently. Given this, there's no real reason to make them visually similar. If anything, that would lead to more confusion, not less, because Rust programmers would expect Mojo references to behave the same as Rust references, which they don't, by design. But the "actual" reason that Mojo doesn't use &' syntax is that in Rust, &T is a built-in type, whereas in Mojo, the equivalent type is Pointer which is not a built-in type. It's just a struct, so it's given a regular name, like other structs.
Chris Lattner
Chris Lattner2mo ago
Hey @jmky thanks for the feedback. To +1 other's comments, Mojo isn't Rust and its references work differently. To give you an different example, we don't use foo<x> for generics like C++ does, we use foo[x] . This isn't out of "disdain", it is because the languages are different in their core syntax.
rd4com
rd4com2mo ago
It is just about parameters (you'll see as you read). There is also that thing called progressive exposure to complexity, i like that there is a progressive way of learning as you build/create, and it requires consistent choices that makes sense: (note how lifetimes are parameters, and how lifetimes can also be parametrized on mutability)
# print
def main():
print("hello world")

# create variable (not learned types yet)
def main2():
a = "hello world"
b = 1
print(a, b)

# print trough function
def printer(a):
print(a)
def main3():
a = "hello world"
printer(a)

# modify variables
def add_one(inout a: Int):
a+=1
def main4():
a = 0
add_one(a)
print(a)

# here there is another gradual step (currently worked on)
# (create auto dereferenced reference and not learned lifetime yet)

# create safe reference (not learned lifetime yet)
def main5():
a = 0
b = Pointer.address_of(a)
a+=1
print(b[])

# return auto dereference (not learned lifetime yet)
def return_ref(arg: List[Int])->ref[arg]List[Int]:
return arg

def main6():
a = List(1,2,3)
a.append(4)
print(return_ref(a)[3])

# return mutable auto dereference (not learned lifetime yet)
def return_mut_ref(inout arg: List[Int])->ref[arg]List[Int]:
return arg

def main7():
a = List(1,2,3)
return_mut_ref(a).append(4)
print(len(a))


# return inferred mutability auto dereference (not learned lifetime yet)
def return_inferred_mut_ref(ref[_]arg: List[Int])->ref[arg]List[Int]:
return arg

def main8():
a = List(1,2,3)
return_mut_ref(a).append(4)
print(len(a))

# return immutable auto dereference
def return_immutable_ref[L:ImmutableLifetime](ref[L]arg: List[Int])->ref[L]List[Int]:
return arg

def main9():
a = List(1,2,3)
a.append(4)
print(return_immutable_ref(a)[4])
print(len(a))


# return mutable reference
def return_mutable_ref2[L:MutableLifetime](ref[L]arg: List[Int])->Pointer[List[Int],L]:
return Pointer.address_of(arg)

def main10():
a = List(1,2,3)
b = return_mutable_ref2(a)
c = b
c[].append(4)
print(len(a))
# print
def main():
print("hello world")

# create variable (not learned types yet)
def main2():
a = "hello world"
b = 1
print(a, b)

# print trough function
def printer(a):
print(a)
def main3():
a = "hello world"
printer(a)

# modify variables
def add_one(inout a: Int):
a+=1
def main4():
a = 0
add_one(a)
print(a)

# here there is another gradual step (currently worked on)
# (create auto dereferenced reference and not learned lifetime yet)

# create safe reference (not learned lifetime yet)
def main5():
a = 0
b = Pointer.address_of(a)
a+=1
print(b[])

# return auto dereference (not learned lifetime yet)
def return_ref(arg: List[Int])->ref[arg]List[Int]:
return arg

def main6():
a = List(1,2,3)
a.append(4)
print(return_ref(a)[3])

# return mutable auto dereference (not learned lifetime yet)
def return_mut_ref(inout arg: List[Int])->ref[arg]List[Int]:
return arg

def main7():
a = List(1,2,3)
return_mut_ref(a).append(4)
print(len(a))


# return inferred mutability auto dereference (not learned lifetime yet)
def return_inferred_mut_ref(ref[_]arg: List[Int])->ref[arg]List[Int]:
return arg

def main8():
a = List(1,2,3)
return_mut_ref(a).append(4)
print(len(a))

# return immutable auto dereference
def return_immutable_ref[L:ImmutableLifetime](ref[L]arg: List[Int])->ref[L]List[Int]:
return arg

def main9():
a = List(1,2,3)
a.append(4)
print(return_immutable_ref(a)[4])
print(len(a))


# return mutable reference
def return_mutable_ref2[L:MutableLifetime](ref[L]arg: List[Int])->Pointer[List[Int],L]:
return Pointer.address_of(arg)

def main10():
a = List(1,2,3)
b = return_mutable_ref2(a)
c = b
c[].append(4)
print(len(a))
It would not make sense to move lifetimes out of the parameter world, because things can be parametrized on lifetime, and lifetimes can be parametrized. As a superset of python, mojo have theses [] for generics(parametrization), that's all there is to it really 👍
Thomas Børstad
Thomas Børstad2mo ago
Thanks for that code example @rd4com, very useful for me 🙏
capt_falamer
capt_falamer2mo ago
this kind of step by step introduction from simple to advanced features would be great to add to the mojo documentation. it would be especially helpful to the python crowd or even people new to programming
Darin Simmons
Darin Simmons2mo ago
@ivellapillil I think @rd4com did a nice job , food for thought for the book.
ivellapillil
ivellapillil2mo ago
Hi Darin, thanks for the suggestion. I was also trying to introduce concepts gradually in the book... Or is there something that is confusing or introduced a bit too early which needs adjustment?
Darin Simmons
Darin Simmons2mo ago
Last we talked, I thought you were still waiting on some of the unfinished design bits to finish. I could be totally wrong but I thought this was among them. Admittedly, I hadn't give back to check.
ivellapillil
ivellapillil2mo ago
Yes. There are some design work that is in flux at the moment.. As soon as they are stable, I would get back to incorporating them in.. Excited to see things around lifetime becoming more easier to explain..
bpr
bpr2mo ago
Why is it that when we store the result of return_immutable_ref from main9 into a variable, we get a mutable deep copy of the original list?
from collections import InlineList

# Utility fn for printing list
fn fmt_list[T: StringableCollectionElement](l: List[T]) -> String:
if len(l) == 0:
return "[]"
var res: String = "["
res += str(l[0])
for i in range(1, len(l)):
res += ", "
res += str(l[i])
res += "]"
return res

# return immutable auto dereference
fn return_immutable_ref[L:ImmutableOrigin](ref[L]arg: List[String])->
ref[L]List[String]:
return arg

fn main():
var a: List[String] = List(str("One"), str("Two"), str("Three"))
var imm_a = return_immutable_ref(a)
a[1] += "2"
print("a is ", fmt_list(a))
print("imm_a is ", fmt_list(imm_a))
var il = InlineList[String]("Four", "Five", "Six")
for s in il:
imm_a.append(str(s[]))
imm_a[0] += "1"
print("a is ", fmt_list(a))
print("imm_a is ", fmt_list(imm_a))
from collections import InlineList

# Utility fn for printing list
fn fmt_list[T: StringableCollectionElement](l: List[T]) -> String:
if len(l) == 0:
return "[]"
var res: String = "["
res += str(l[0])
for i in range(1, len(l)):
res += ", "
res += str(l[i])
res += "]"
return res

# return immutable auto dereference
fn return_immutable_ref[L:ImmutableOrigin](ref[L]arg: List[String])->
ref[L]List[String]:
return arg

fn main():
var a: List[String] = List(str("One"), str("Two"), str("Three"))
var imm_a = return_immutable_ref(a)
a[1] += "2"
print("a is ", fmt_list(a))
print("imm_a is ", fmt_list(imm_a))
var il = InlineList[String]("Four", "Five", "Six")
for s in il:
imm_a.append(str(s[]))
imm_a[0] += "1"
print("a is ", fmt_list(a))
print("imm_a is ", fmt_list(imm_a))
jmky
jmkyOP2mo ago
https://blog.rust-lang.org/2024/10/17/Rust-1.82.0.html Rust is also a pragmatic language. You can now put the safe keyword in unsafe extern block so that your clients don't need to litter unsafe in their codes.
Announcing Rust 1.82.0 | Rust Blog
Empowering everyone to build reliable and efficient software.
Want results from more Discord servers?
Add your server