Hey, I was just playing around with Abilities and found some crash when I have a unused value that is also still a type variable and not resolved to a concrete type:
app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br" }
import pf.Stdout
import pf.Arg exposing [Arg]
ExampleAbility implements
unpack : a -> actualValue where a implements ExampleAbility
StringWithAbility := Str implements [ExampleAbility { unpack: readString }]
readString : StringWithAbility -> Str
readString = |@StringWithAbility txt| txt
readAbility : anyExampleAbility -> b where anyExampleAbility implements ExampleAbility
readAbility = |strg| unpack(strg)
main! : List Arg => Result {} _
main! = |_args|
unpacked = readAbility(@StringWithAbility("test"))
Stdout.line!("Hello !")
If I hover over unpacked
it shows me b
as the type.
The compiler crashes with
thread 'main' panicked at crates/compiler/mono/src/borrow.rs:361:34:
internal error: entered unreachable code:
No borrow signature for LambdaName { name: `#UserApp.readAbility`, niche: Niche(Captures([])) } layout.
Tip 1: This can happen when you call a function with fewer arguments than it expects.
Like `Arg.list!` instead of `Arg.list! {}`.
Tip 2: `roc check yourfile.roc` can sometimes give you a helpful error.
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
As soon as I use the unpacked
it is resolved to Str
and no crash occurs.
Interesting :thinking:
My immediate thoughts:
I think your actualValue
needs to be restricted.
Either, you need to make it Str
, or you need to give it an ability to say what you can validly do with an actualValue
Think of it like this. If I told you that I am going to give you something, but you have no idea what it would be, what valid actions can you make with what I give you.
The answer is you can essentially do nothing cause you know nothing about what I am giving you.
That is the case here with this ability. You are saying that you are return an unknown type variable with no specification. As such, you should not be able to do anything with it. It is actually likely a big that it can become a Str
cause there is no guarantee of that.
The error you should be getting is something like
Type mismatch: Your function requires a
Str
but you were given aactualValue
.actualValue
can not be restricted to be aStr
.
Hopefully this makes some sense
Anyway, definitely at a minimum, the errors here being bad and some things working are each compiler bugs, but they are for the old rust compiler and likely not worth trying to fix at this point.
We definitely can help you work around issue like this and build a useful program.
Oh, one other point of clarification, because of how binding works for ability type variables. If you type unpacked
(or use it in a way that demands a specific type), roc will anchor to that typing. It is kinda like a form of late binding that is special to output types of abilities. This is how Decode
is able to work at all. But due to it being a late binding anchor, it has lots of edge cases that can easily be underdefined or fail to type check. (Which is the error case here)
I see, thank you.
I would have assumed unpacked
can know that it is a str
and not a actualValue
due to it using the StringWithAbiliy
s readString
.
Right now it seems typing it works kind of like an asInstanceOf
of a any
value. E.g. I can do this and it compiles fine (unpacke
is seen as num type):
main! = |_args|
unpacked = readAbility(@StringWithAbility("test"))
other = Num.to_str(unpacked)
Stdout.line!("Hello ${other}!")
For my usecase, let's say I would want to have an ability "returns the most important value of a record" (of an opaque type).
mostImportant: record -> a where record implements MostImportantAbility
won't work, same as above.
How would I define this ability? Or does this only make sense in that I also give a
an ability that at sometime resolves to a concrete type?
Interesting. What is the end goal here?
Any yeah, that ability will only work if you always type the output when calling it.
I was looking into implementing CRDTs in roc, seemed like an interesting enough use-case to lean more about the language.
My idea was to define a Mergeable/CRDT
ability. Then this could be implemented for a counter or a record and so on.
Initially I was looking into an API like this
Mergeable implements
read : a -> actualValue where a implements Mergeable
mergeWithRemote : a, remoteUpdate -> a where a implements Mergeable
update : a, localUpdate -> (a, remoteUpdate) where a implements Mergeable
But now I am not sure anymore if that approach makes sense :slight_smile:
Last updated: Oct 18 2025 at 12:13 UTC