I have this code, and my question is, why User.welcome(guest) works when guest.welcome() is not allowed?
What is the type of guest here? My current assumption is that User is a type, and LoggedIn, Guest are constructors of this type(like it would be in Gleam).
But the fact that this example doesn't work makes me think that branches are real types with some kind of sub typing from User.
app [main!] { pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.6/2BfGn4M9uWJNhDVeMghGeXNVDFijMfPsmmVeo6M4QjKX.tar.zst" }
import pf.Stdout
import pf.Stdin
# 2 tagged union
User := [
LoggedIn({name: Str}),
Guest
].{
welcome = |self| match (self) {
Guest => dbg "Welcome Guest"
LoggedIn(name) => dbg "Welcome Guest".concat(name)
}
}
main! = |_a| {
guest = Guest()
User.welcome(guest)
guest.welcome() // ERROR
Ok({})
}
UPD
This works:
# guest : User
guest = Guest()
User.welcome(guest)
and that:
guest : User
guest = Guest()
User.welcome(guest)
-- TYPE MISMATCH ---------------------------------
The first argument being passed to this function has the wrong type:
┌─ main.roc:35:3
│
35 │ User.welcome(guest)
│ ^^^^^
This argument has the type:
User
But main.User.welcome needs the first argument to be:
[Guest, LoggedIn(Str)]
Which really means that hmm... that Guest + LoggedIn != User?
Question 2 kinda. When Im trying to add type for
welcome = |self| match (self) {
Guest => dbg "Welcome Guest"
LoggedIn(name) => dbg "Welcome Guest".concat(name)
}
welcome : User -> {}
It has the type:
User => {}
But the annotation say it should be:
User -> {}
Hint: This function is effectful, but a pure function is expected.
But
The method concat has the type:
Str, Str -> Str
Which means dbg makes functions dirty? I was thinking that using dbg instead of stdout print is the main reason to "cheat", like dbg statemets will be erased in release mode so functions stays pure, just like in Nim.
And when I change type to -> Str, and remove dbg
welcome : User => Str
welcome = |self| match (self) {
Guest => "Welcome Guest"
LoggedIn(name) => "Welcome Guest".concat(name)
}
Im getting
-- TYPE MISMATCH ---------------------------------
The concat method on Str has an incompatible type:
┌─ main.roc:25:24
│
25 │ LoggedIn(name) => "Welcome Guest".concat(name)
│ ^^^^^^^^^^^^^^^
The method concat has the type:
Str, Str -> Str
But I need it to have the type:
Str, { name: Str } -> _ret
What does it means?
hey there! thanks for the detailed write-up, I can answer both questions!
for question 2, it needs to be:
LoggedIn(user) => "Welcome Guest".concat(user.name)
in the original version it has LoggedIn(name) => "Welcome Guest".concat(name), which is trying to concat a Str and the payload of LoggedIn, which is {name : Str}
I'm not sure why dbg is making the function effectful, that seems like a bug
for question 1:
the root cause is the same as question 2! the error messages says:
This argument has the type:
User
But main.User.welcome needs the first argument to be:
[Guest, LoggedIn(Str)]
which is correct, because User.LoggedIn has the type: LoggedIn({name: Str})
If you update the match statement for the logged-in user to match what I have above, your original example type checks:
guest = User.Guest # also, the () is not needed here
User.welcome(guest)
guest.welcome()
I think there's definitely an opportunity here to improve the error message in this case, though, to highlight that the payloads of LoggedIn do not match.
It took me a couple reads to see what the problem was there - this seems like a great case for a compiler hint? Something like
guest.welcome()
^^^^^^^
hint: The function `welcome` was found in the module `User`, but its
first argument does not accept `User` so cannot be used as a method
I will make some issues :)
Thanks! I was thinking that match destructs the struct inside, so I can do smth like
LoggedIn(name, age)
since I have background of langs with tagged unions in which fields can be attached directly to .. tag, not single opaque struct that need to be destructed.
Hmm, I dont get it, maybe another opportunity for better error :)
There is no error in welcome so I guess the destructing of name is correct.
The error is on the call side. (and Ik that {} means () Unit)
User := [
LoggedIn({name: Str}),
Guest
].{
# 10 switch
welcome : User => Str
welcome = |self| match (self) {
Guest => "Welcome Guest"
LoggedIn({name}) => "Welcome Guest".concat(name)
}
}
main! = |_a| {
person: Person
person = {name: "String", age: 24}
p2 = person->have_birthday()
dbg p2
guest : User
guest = Guest()
User.welcome(guest)
# guest.welcome()
Ok({})
}
❯ roc main.roc ✘ 1 master ✖ ✱
-- TYPE MISMATCH ---------------------------------
This expression produces a value, but it's not being used:
┌─ main.roc:36:3
│
36 │ User.welcome(guest)
│ ^^^^^^^^^^^^^^^^^^^
It has the type:
Str
Since this expression is used as a statement, it must evaluate to {}.
If you don't need the value, you can ignore it with _ =.
Found 1 error(s) and 0 warning(s) for main.roc.
If I comment out the #User.welcome(guest) Im getting only a warning about unused guest(not an error!)
~/D/F/L/Roc ❯❯❯ roc main.roc ✘ 1 master ✖ ✱
-- UNUSED VARIABLE -------------------------------
Variable guest is not used anywhere in your code.
If you don't need this variable, prefix it with an underscore like _guest to suppress this warning.
The unused variable is declared here:
┌─ main.roc:34:3
│
34 │ guest = Guest()
│ ^^^^^
Found 0 error(s) and 1 warning(s) for main.roc.
dbg: { age: 25, name: "String" }
Okay, lets uncomment it back and try to use the advice from the error you can ignore it with _ =.
guest = Guest()
_ = User.welcome(guest)
> got bommbarded with 5 errors, and same advice again
-- UNEXPECTED TOKEN IN EXPRESSION ----------------
The token _ is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
┌─ main.roc:35:3
│
35 │ _ = User.welcome(guest)
│ ^
-- UNEXPECTED TOKEN IN EXPRESSION ----------------
The token = is not expected in an expression.
Expressions can be identifiers, literals, function calls, or operators.
┌─ main.roc:35:5
│
35 │ _ = User.welcome(guest)
│ ^
-- UNRECOGNIZED SYNTAX ---------------------------
I don't recognize this syntax.
┌─ main.roc:35:3
│
35 │ _ = User.welcome(guest)
│ ^
This might be a syntax error, an unsupported language feature, or a typo.
-- UNRECOGNIZED SYNTAX ---------------------------
I don't recognize this syntax.
┌─ main.roc:35:5
│
35 │ _ = User.welcome(guest)
│ ^
This might be a syntax error, an unsupported language feature, or a typo.
-- TYPE MISMATCH ---------------------------------
This expression produces a value, but it's not being used:
┌─ main.roc:35:7
│
35 │ _ = User.welcome(guest)
│ ^^^^^^^^^^^^^^^^^^^
It has the type:
Str
Since this expression is used as a statement, it must evaluate to {}.
If you don't need the value, you can ignore it with _ =.
Found 5 error(s) and 0 warning(s) for main.roc.
Maybe this text of the error is from the old roc, and not valid in new one? thats my guess.
Also why its so inconsistent, that unused result of a function is an error, and unused type constructor is a warrrning?
Not to say whether or not that's clear from the error messages given, but I see it as something like:
I didn't realise you couldn't assign to _ currently :thinking: It works for me if I write _myvarname = User.welcome(guest), and _ also works in matches.
I'm pretty sure we intended to support _ = something, I will implement it right now.
since I have background of langs with tagged unions in which fields can be attached directly to .. tag
I think the current approach falls in line with our desire to have a small number of simple language primitives that can easily be combined, but I will keep an ear out for more feedback on this.
Anton said:
I'm pretty sure we intended to support
_ = something, I will implement it right now.
Done in PR#9332
Last updated: Apr 10 2026 at 12:38 UTC