Confusion with owned integers

I tried the replacing String from the example in the docs and got an error when running build. Docs example
fn set_fire(owned text: String) -> String:
text += "🔥"
return text

fn mojo():
let a: String = "mojo"
let b = set_fire(a)
print(a)
print(b)

mojo()
fn set_fire(owned text: String) -> String:
text += "🔥"
return text

fn mojo():
let a: String = "mojo"
let b = set_fire(a)
print(a)
print(b)

mojo()
My modified example
fn take_ownership_int(owned x: Int) -> Int:
x += 5
return x


fn main():
let x = 10
let y = take_ownership_int(x)
print("x = " + String(x) + " and y = " + String(y))
fn take_ownership_int(owned x: Int) -> Int:
x += 5
return x


fn main():
let x = 10
let y = take_ownership_int(x)
print("x = " + String(x) + " and y = " + String(y))
Error
❯ mojo build hello-world/take_ownership.mojo
/path/to/hello-world/take_ownership.mojo:9:26: error: use of uninitialized value 'x'
print("x = " + String(x) + " and y = " + String(y))
^
/path/to/hello-world/take_ownership.mojo:7:5: note: 'x' declared here
let x = 10
^
mojo: error: failed to run the pass manager
❯ mojo build hello-world/take_ownership.mojo
/path/to/hello-world/take_ownership.mojo:9:26: error: use of uninitialized value 'x'
print("x = " + String(x) + " and y = " + String(y))
^
/path/to/hello-world/take_ownership.mojo:7:5: note: 'x' declared here
let x = 10
^
mojo: error: failed to run the pass manager
--- If I replace Int with String it builds ok.
fn take_ownership_str(owned x: String) -> String:
x += " mojo"
return x


fn main():
let hello = "hello"
let hello_mojo = take_ownership_str(hello)
print("hello = " + hello + " and hello_mojo = " + hello_mojo)
fn take_ownership_str(owned x: String) -> String:
x += " mojo"
return x


fn main():
let hello = "hello"
let hello_mojo = take_ownership_str(hello)
print("hello = " + hello + " and hello_mojo = " + hello_mojo)
22 Replies
b4rdarian
b4rdarian12mo ago
Btw the error is similar to what I get if I call
take_ownership_str(hello^)
print(hello)
take_ownership_str(hello^)
print(hello)
᲼᲼
᲼᲼12mo ago
You are passing the ownership from one scope to another and thus this variable gets "consubed" by a function I think it's similar to Rust, but I'm not perfect not in Rust nor in Mojo, these are just my thoughts
ModularBot
ModularBot12mo ago
Congrats @᲼᲼, you just advanced to level 2!
Stole
Stole12mo ago
Yeah, this is correct You can't use the original one declared because that was consumed by your function call, albeit it did pass ownership back when you assigned it to y in your Int example Even though you do have that object under y, x is now invalid I think the compiler's behavior on this isn't fully fleshed out, or blocked on traits, and that's why it worked with String when it ideally shouldn't
rdickert
rdickert12mo ago
@b4rdarian I tried the above code but changed let x to var x:
def check_int():
var x = 10
let y = take_ownership_int(x)
print("x = " + String(x))
print("y = " + String(y))

check_int()
def check_int():
var x = 10
let y = take_ownership_int(x)
print("x = " + String(x))
print("y = " + String(y))

check_int()
This works and prints x=10 and y=15, but there is a compiler warning: 'x' was declared as a 'var' but never mutated, consider switching to a 'let' So making x mutable unblocks the code and clearly copies x as expected, but the compiler tells us to switch to let, which gives a compiler error. The docs note: "Currently, Mojo always makes a copy when a function returns a value." So I'm not quite sure where this leaves us.
b4rdarian
b4rdarian12mo ago
I think the compiler's behavior on this isn't fully fleshed out, or blocked on traits, and that's why it worked with String when it ideally shouldn't
The example with the String was taken from the docs. Either the docs are wrong or I am missing something about how owned should behave. Further down in the example it talks about how using ^ actually gives complete ownership, making the passed variable out of scope.
gabrieldemarmiesse
I think that without ^, if the argument is owned, it should just make a copy (which seems pretty logical to me). Maybe the behavior is changing? I wouldn't be surprised if it changed more in the future, they are working on it indeed.
b4rdarian
b4rdarian12mo ago
I suspect it relates to how Ints are actually stored under the hood with comparison to String objects. I played a bit with another example from the docs (https://docs.modular.com/mojo/programming-manual.html#transfer-arguments-owned-and) yesterday,
Modular Docs - Mojo🔥 programming manual
A tour of major Mojo language features with code examples.
b4rdarian
b4rdarian12mo ago
Added a __copyinit__ to UniquePointer to allow copying.
struct UniquePointer:
var ptr: Int

fn __init__(inout self, ptr: Int):
self.ptr = ptr

# Allows us to move ownership, eg owned_argument(UniquePointer(123)^)
fn __moveinit__(inout self, owned existing: Self):
self.ptr = existing.ptr

# Allows us to pass by copy, eg owned_argument(UniquePointer(123))
fn __copyinit__(inout self, existing: Self):
self.ptr = existing.ptr

fn __del__(owned self):
self.ptr = 0
struct UniquePointer:
var ptr: Int

fn __init__(inout self, ptr: Int):
self.ptr = ptr

# Allows us to move ownership, eg owned_argument(UniquePointer(123)^)
fn __moveinit__(inout self, owned existing: Self):
self.ptr = existing.ptr

# Allows us to pass by copy, eg owned_argument(UniquePointer(123))
fn __copyinit__(inout self, existing: Self):
self.ptr = existing.ptr

fn __del__(owned self):
self.ptr = 0
From the example
fn take_ptr(owned p: UniquePointer):
print_no_newline("take_ptr: ")
print(p.ptr)


fn use_ptr(borrowed p: UniquePointer):
print_no_newline("use_ptr: ")
print(p.ptr)
fn take_ptr(owned p: UniquePointer):
print_no_newline("take_ptr: ")
print(p.ptr)


fn use_ptr(borrowed p: UniquePointer):
print_no_newline("use_ptr: ")
print(p.ptr)
Last call will fail as expected if uncommented.
fn owned_and_borrowed_example():
let p = UniquePointer(1234)
use_ptr(p)
take_ptr(p)
use_ptr(p)
take_ptr(p ^)
# This will cause a compile error as `p` is no longer in scope
# The `^` operator ends the lifetime of the value being binded to `p`.
# use_ptr(p)
fn owned_and_borrowed_example():
let p = UniquePointer(1234)
use_ptr(p)
take_ptr(p)
use_ptr(p)
take_ptr(p ^)
# This will cause a compile error as `p` is no longer in scope
# The `^` operator ends the lifetime of the value being binded to `p`.
# use_ptr(p)
These will fail compilation if the the commented lines are uncommented
fn owned_and_borrowed_int():
let x: Int = 1234
use_int(x)
take_int(x)
# use_int(x)


fn owned_and_borrowed_bool():
let x: Bool = False
use_bool(x)
take_bool(x)
# use_bool(x)


fn owned_and_borrowed_float():
let x: Float32 = 1234.0
use_float(x)
take_float(x)
# use_float(x)
fn owned_and_borrowed_int():
let x: Int = 1234
use_int(x)
take_int(x)
# use_int(x)


fn owned_and_borrowed_bool():
let x: Bool = False
use_bool(x)
take_bool(x)
# use_bool(x)


fn owned_and_borrowed_float():
let x: Float32 = 1234.0
use_float(x)
take_float(x)
# use_float(x)
ModularBot
ModularBot12mo ago
Congrats @b4rdarian, you just advanced to level 1!
b4rdarian
b4rdarian12mo ago
I think the docs need updating / clarification. I believe it relates to stack vs heap. So copying only works for objects stored in the heap.
Weichen-16小時的自由人
I'm encountering the same problem. I'm using modular playground. The version of mojo kernel should be v0.4.0 at the time.
According to Argument mutability and ownership I modified the function from the example in the docs.
My modified example:
fn add_owned(owned x: Int, owned y: Int) -> Int:
x += 10
y += 20
return x + y
fn add_owned(owned x: Int, owned y: Int) -> Int:
x += 10
y += 20
return x + y
Situation 1: a and b are declared as var
fn main_1():
var a: Int = 100
var b: Int = 200
print("a: ", a, "b: ", b)
let c = add_owned(a, b)
print("c: ", c)
print("a: ", a, "b: ", b)

main_1()
fn main_1():
var a: Int = 100
var b: Int = 200
print("a: ", a, "b: ", b)
let c = add_owned(a, b)
print("c: ", c)
print("a: ", a, "b: ", b)

main_1()
The result:
a: 100 b: 200
c: 330
a: 100 b: 200
a: 100 b: 200
c: 330
a: 100 b: 200

It works.
Situation 2 a and b are declared as let
fn main_2():
let a: Int = 100
let b: Int = 200
print("a: ", a, "b: ", b)
let c = add_owned(a, b)
print("c: ", c)
print("a: ", a, "b: ", b)

main_2()
fn main_2():
let a: Int = 100
let b: Int = 200
print("a: ", a, "b: ", b)
let c = add_owned(a, b)
print("c: ", c)
print("a: ", a, "b: ", b)

main_2()
The result:
expression failed to parse (no further compiler diagnostics)
expression failed to parse (no further compiler diagnostics)
It looks like Mojo didn't make copies of a and b separately.
Modular Docs - Mojo language basics
A short introduction to the Mojo language basics.
Weichen-16小時的自由人
So, did you mean it is not a bug?
b4rdarian
b4rdarian12mo ago
I can't really say. It might be, all I am saying is that it doesn't work as it's described in the docs for primitive types.
b4rdarian
b4rdarian12mo ago
In that integers are copied rather than owned. You might be right and it's a bug. I am on an M2 MacBook.
❯ mojo --version
mojo 0.4.0 (9e33b013)
❯ mojo --version
mojo 0.4.0 (9e33b013)
VSCode's mojo extension is not picking up on the error. Only when trying to compile.
rdickert
rdickert12mo ago
@b4rdarian great research. I think this must be on the right track. This is probably worth filing an issue on the repo
b4rdarian
b4rdarian12mo ago
Good call. Will do.
b4rdarian
b4rdarian12mo ago
Apparently I am not the first to notice. There is already an issue open https://github.com/modularml/mojo/issues/747. I can see @Weichen-威辰 has added his experience to it.
GitHub
Issues · modularml/mojo
The Mojo Programming Language. Contribute to modularml/mojo development by creating an account on GitHub.
b4rdarian
b4rdarian12mo ago
I will do the same.
Weichen-16小時的自由人
Thanks for the link. It helps me to get a better understanding of ownership.
ModularBot
ModularBot12mo ago
Congrats @Weichen-威辰, you just advanced to level 1!
Want results from more Discord servers?
Add your server