Hello,
I am coming from nodes/js/ts environment and just stumble upon Roc.
I am sorry for being stupid, but I somehow cannot wrap my head about the relationship between core Roc lang and platform. How is it comparable to other languages where you typically have the lang itself, some standard library and various frameworks on top of that?
Also as I understand from few posts, Roc on its own cannot handle IO and is reliant on platform. Isn't it quite limiting to not have such important feature in the basic package? Is there some strong reason to have it this way?
G'day @Michal Timko , welcome :wave:
Have you seen this guide? https://www.roc-lang.org/platforms
I read through that.
It looks like platform is something like both external library and interface between Roc and another lang, that actually compile some executable from the code.
But the it raise another questions like if it doesn't make Roc just nice interface for Rust and what is possible to do with Roc without platform (can I even compile it to executable without platform?)
So for me is unclear what type of entity the platform is and what I can expect from it. But maybe when i try to build something, it will start making sense.
Yeah, I definitely recommend you try roc out. Maybe checkout the basic-cli and basic-webserver first. You could then also look at roc-wasm4 (a game platform), or one of the others.
My background is web developer, so my first intuition is "how to build server with this?"
And when I read a bit about platforms and the IO limitations, second question was "Can I build https server with this, if I need both web based api and IO in one platform?"
So I will see, what I can do.
@Michal Timko I think Roc's platforms make more explicit the relationship we have to lower level languages. The reason we use JS to write webservers is because we don't want to think about how to do stuff like manage memory or access resources, we want a PL that has done that adequately for us.
Roc functions as a language that is easy to write in and think about, and actively talks to someone else for the important stuff. But I'd rather write my logic in an easy language like Roc and have Rust do the hard stuff that needs to be done particularly and efficiently
JS doesn't go quite as far, but there are a lot of "performance critical" libs written in C that get wrapped in JS. Platforms work a similar way to that FFI
Shameless plug for my demo app https://github.com/lukewilliamboswell/roc-htmx-tailwindcss-demo
If you're coming from a web background, and looking for something to hack around with... you're welcome to play with that and if you want to make a PR with any features I'd be happy to review.
The goal of that demo is to be a good starting point for building web apps with roc, and demonstrate how to use familiar technologies like htmx and tailwindcss.
There's a lot more we could add in there, I just get distracted easily with other things :smiley:
Thank you,
will look into it.
Can I build https server with this, if I need both web based api and IO in one platform?
basic-webserver has both, it support for example File.write, Stdout.line and Http.get, ...
Yeah...I already realize it too. It just that in one speech Feldman mention something along the line that you probably don't need IO for web based apps, so I just assume that web oriented platforms are without IO...silly me.
I am writing a basic platform that calls into libsecp256k1 to do some crypto stuff. I have a platform/host.c with some LLM help. it seems like there is a naming convention like roc_fx_blah that allows calling C functions from Roc. is this documented?
Tell me where I'm wrong: in modern platform Roc, i would create a function signature, within a module, named with an exclamation mark. this means impure (does it directly imply it's a host-implemented function?). all calls into the host are impure. if roc sees this with no def, it will construct a symbol based on the function name, and try to call that symbol. If I had module Rick, function hull!, then roc constructs roc_fx_rickHull (note the casing and lack of underscores) and I would need to have this defined in my host.c
i ran glm on codebase and this is what it said:
There's a Zig-based platform in test-platform-effects-zig/host.zig. Looking at the code, the function naming follows
the same convention:
The naming convention is:
• Strip the ! from the Roc function name
• Prefix with roc_fx_
• No CamelCase conversion - underscores stay as-is
Examples from the Zig host:
zig
pub export fn roc_fx_get_line() str.RocStr { ... }
pub export fn roc_fx_put_line(rocPath: *str.RocStr) i64 { ... }
pub export fn roc_fx_get_int() GetInt { ... }
pub export fn roc_fx_id_effectful(input_num: u64) u64 { ... }
So if you had a Roc module with Rick.hull!, the Zig host would define:
zig
pub export fn roc_fx_hull(...) {
// your implementation
}
The key points:
1. ! suffix = impure function (side effects)
2. roc_fx_ prefix = foreign function that Roc calls
3. No casing conversion - original identifier name is preserved
4. Must be marked pub export in Zig to be callable from Roc
The calling convention is documented in https://github.com/roc-lang/roc/blob/2ca9919d86d429751149bd8877e57e9ecbc7243f/src/builtins/host_abi.zig
A type module with an annotation only definition is a "hosted effect".
Ah... are you talking about the new compiler or the old rust one?
They're quite different
Host calling into Roc -- these are the entrypoints which are defined in the platform header "provides" section
Roc calling back into Host -- these are the hosted effects, i.e. functions without definition like hull! : Str => {}
Thanks, this is helpful! so the module name is no longer included? I suppose I am asking about hosted effects. also, I am currently using C and gcc on the host side. i haven't actually installed roc yet, heh
https://gist.github.com/rickhull/e4ae108e4f00de5582f37689146003c0 how does this look?
also, where does the zig (rust?) for basic-cli live? i was looking for e.g. platform/host.zig is that an old convention?
The basic-cli platform is still in the process of being migrated to the new compiler, you can see the work here https://github.com/roc-lang/basic-cli/pull/413
My roc-platform-template-zig and roc-platform-template-rust are both already upgraded to the new compiler.
Yes the module keyword has been deprecated. We now use "type modules" which are centered around a single nominal type with associated functions, like
Sqlite := [].{
...
}
All of your syntax is for the old rust compiler... are you wanting to write a platform for that or the new compiler?
the new compiler is more fun, join us :rolling_on_the_floor_laughing:
I think I should probably keep it simple and use whatever is most documented. i might then migrate to the new compiler. it won't be much code
I'd recommend the new compiler, it's a much more sane experience for platform authors.
what are some good approaches for length validation? for example, a 32 byte SHA256 result. my current understanding is this would commonly be represented as List U8. I have come up with a way to make a Digest type that does some length validation. basically an opaque type, accompanied by a constructor function that does the validation.
If I wanted to guarantee something was a fixed size, I would use a nominal record
SHA256 :: {
field1: U8,
field2: U8,
field3: U8,
field4: U8,
field5: U8,
....
}.{
## put methods here to work with etc
}
Another advantage of this over a list is that it isn't refcounted
hmmm, i would never want to address the individual fields. but this is interesting.
i am calling into libsecp256k1; is it sensible to stick with C, or should I try Zig? I am a bad C programmer ;)
zig zig zig
@Luke Boswell can I strongly request that your platform templates be added to: https://www.roc-lang.org/platforms where it says:
Anyone can implement their own platform. There isn't yet an official guide about how to do this, so the best way to get help if you'd like to create a platform is to say hi in the
#beginnerschannel on Roc Zulip!
I will improve the docs there
https://github.com/roc-lang/www.roc-lang.org/pull/46
I am still working on my libsecp256k1 platform, but I started from roc-platform-template-zig this time, and I'm using the latest nightly compiler. however, I am getting a lot of weird errors that don't agree with the tutorial or stdlib. e.g.
Error: Exit code 1
roc hello_world.roc
-- DOES NOT EXIST --------------------------------
Num.to_str does not exist.
┌─ hello_world.roc:19:36
│
19 │ Stdout.line!("pubkey length: ${Num.to_str(len)}")
│ ^^^^^^^^^^
https://gist.github.com/rickhull/7e3c2468f2327853d29b00391824cf63 why might this be happening?
The tutorial and stdlib docs are for roc alpha 4, for the new nightlies you can look at:
I see -- and this implied by the redirect URL. now, it looks like I have roc-src on one commit (from Jan 1) but my compiler is from Jan 15. going to fix this and retry with the proper builtin calls
If you are using the nightly compiler from https://github.com/roc-lang/nightlies/releases you probably do not need roc-src as well
I think Luke used roc-src for easy debugging
to what extent is the tutorial specific to alpha-4? is it just the builtin calls or is it more subtle?
It's quite broad, alpha4 has different syntax, different features, different builtins. For a quick comparison between alpha4 and the new compiler(=nightlies) you can check https://github.com/roc-lang/roc/blob/main/test/fx/all_syntax_test.roc vs https://github.com/roc-lang/examples/blob/main/examples/AllSyntax/main.roc (alpha4)
What do we think about possibly adopting Just (justfile) instead of shell scripts? I have only just started using it (heh), and I've found it fantastically easy and helpful. for example, instead of roc-platform-template-zig/bundle.sh there would be a just bundle task. instead of ci/all_test.sh, just ci
I understand that it's not desirable to force tooling like this on users, but it seems like a nicer fit for my own projects
Just does not come pre-installed on github CI, that's already adding more friction than I would like. Also, I would like to convert the sh scripts to Roc scripts once basic-cli is ported for the new compiler and works well enough. Roc does not come pre-installed on CI but it's good to test our own stuff in the real world :)
good to know. i see that Just also executes each line in its own shell environment, so complex scripts are not suited. I haven't looked at build.roc but I have come across it. and I am also using build.zig.zon so, many things overlap. what is the story with build.roc ?
isn't there some way to specify some existing tools in the GH CI environment, like in a cached image or something?
what is the story with build.roc ?
build.roc should be in charge of building the platform. old Roc did not work well on windows so you may see e.g. .sh files instead.
what about testing conventions? I am vaguely aware of the expect keyword. in my case, I am working on a platform first, so my question is maybe oriented around platforms. i am tempted to make e.g. test/host.roc to test functions in the Host module. I have also seen examples/test.roc
isn't there some way to specify some existing tools in the GH CI environment, like in a cached image or something?
github cache space is precious with only 10 GB which is easily filled with all the different systems we have to support.
i know this is a bad idea, but I wonder if we should use a different forge as the central repo, or even run our own. and mirror to GH. I can imagine it just adds operational headache and expense
Rick Hull said:
what about testing conventions? I am vaguely aware of the
expectkeyword. in my case, I am working on a platform first, so my question is maybe oriented around platforms. i am tempted to make e.g. test/host.roc to test functions in the Host module. I have also seen examples/test.roc
For end-to-end integration tests I typically use expect: https://github.com/roc-lang/basic-cli/blob/main/ci/expect_scripts/bytes-stdin-stdout.exp . I also want to replace that with Roc when possible though.
Example for unit tests: https://github.com/roc-lang/basic-cli/blob/b5bc8ba9435633cd4aeea28d116127eeaea818d2/platform/Url.roc#L513
Rick Hull said:
i know this is a bad idea, but I wonder if we should use a different forge as the central repo, or even run our own. and mirror to GH. I can imagine it just adds operational headache and expense
It's hard to beat free CI :smile:
for my use case: I have just nuke, just clean, just build, etc. i am trying to support an edit-build-test cycle. i currently have a just run, which just does something like roc hello_world.roc or roc main.roc.
so then I have some workflow tasks: just dev (build + run), just fresh (clean + dev). just nuke wipes all cache and is invoked on its own. i think just run is actually a bad idea and I really want just test. this would run "unit tests" that are roughly analogous to smoke tests, doing slightly more than hello world to show the build was successful
test/host.roc, test/sha256.roc, test/sign.roc something like that
I like simple commands too, I have it set up with nix:
roc/src on zig-compiler-benchmarks
❯ nix develop
Restored session: Fri Jan 16 19:58:49 CET 2026
Some convenient commands:
buildcmd = zig build roc
testcmd = zig build snapshot && zig build test
fmtcmd = zig build fmt
covcmd = zig build coverage
cicmd = zig build fmt && ./ci/zig_lints.sh && zig build && zig build snapshot && zig build test && zig build test-playground && zig build coverage
$
I used to run nixos and have used nix in anger, but I emphasize anger. it never clicked for me. but I think there could be some really cool synergy / symmetry with its immutable nature and use in build environments
so it looks like you are really leaning on the zig runner (?) in a way that I'm not. maybe that's a natural direction to go in
so it looks like you are really leaning on the zig runner (?)
Yes for the main repo, a ton is in build.zig. It has been working well for us.
I am brand new to Zig, Roc, and platforms. nearly all of my direct experience is with scripts against runtimes (shell, ruby, elixir, etc). but I've long had an itch for gradual-strong typing (to enforce correctness without being a huge PITA) and deployable artifacts (no runtime)
I guess what I'm getting at is that I started with roc-alpha-4 and C, so I needed a Makefile (ugh!). then I adopted Just (yay!). but now that I'm basing my host on Zig, there is a world of Zig tooling that I'm totally unaware of, analogous but not exactly comparable to the C ecosystem (a good thing!)
I'm signing off for today :wave: but others should be available for additional questions :)
Anton said:
so it looks like you are really leaning on the zig runner (?)
Yes for the main repo, a ton is in build.zig. It has been working well for us.
I use zig build system for every language lol zig4lyfe
Rick Hull said:
I used to run nixos and have used nix in anger, but I emphasize anger. it never clicked for me. but I think there could be some really cool synergy / symmetry with its immutable nature and use in build environments
I have such a like/dislike relationship with nix. On the one hand it think it's conceptually kinda cool. On the other hand, I've usually been able to do something similar much simpler
Rick Hull said:
I am brand new to Zig, Roc, and platforms. nearly all of my direct experience is with scripts against runtimes (shell, ruby, elixir, etc). but I've long had an itch for gradual-strong typing (to enforce correctness without being a huge PITA) and deployable artifacts (no runtime)
I guess what I'm getting at is that I started with roc-alpha-4 and C, so I needed a Makefile (ugh!). then I adopted Just (yay!). but now that I'm basing my host on Zig, there is a world of Zig tooling that I'm totally unaware of, analogous but not exactly comparable to the C ecosystem (a good thing!)
I was building a zig/rust hybrid project the other day, this is a section of my zig build file
// =========================================================================
// Step 1: Build Rust Library
// =========================================================================
const cargo_build = b.addSystemCommand(&[_][]const u8{
"cargo",
"build",
"--release",
"--lib",
"--features",
"gtk",
});
cargo_build.cwd = b.path(".");
Anton said:
Just does not come pre-installed on github CI, that's already adding more friction than I would like. Also, I would like to convert the sh scripts to Roc scripts once basic-cli is ported for the new compiler and works well enough. Roc does not come pre-installed on CI but it's good to test our own stuff in the real world :)
Yeah -- basic cli was such a nice cross-platform scripting setup. We'll be back there soon :smiley:
a /scripts dir full of .roc files would let you claim to be partially self-hosted
I ran into a very confusing issue where my platform/host.zig has hosted_function_ptrs with a specific order, and when I removed some modules from my exposes in platform/main.roc for troubleshooting, the wrong functions were being called. this coupling seems difficult to validate and troubleshoot. have people run into this before?
The hosted functions are those exposed by the platform... so this makes sense in that it's the platform authors responsibility to ensure the function pointers are in the correct order.
This statement probably doesn't help you much though. I think you and I may be the only people who have written a platform in the new compiler @Rick Hull :smiley:
The order is alphabetical based on fully qualified name if I recall.
I'd like to mention also... that the intended design for platform authors is to use roc glue instead of rolling all these manually. We are just in that awkward place in time where the glue subcommand isn't implemented yet for the new compiler so it's all manual
All quite sensible, thanks!
roc glue path/to/generate_c_platform.roc path/to/my/platform/main.roc or something like that and it scaffolds out literally everything you need with a nice type safe (maybe not in C :sweat_smile: ) implementation.
i'm so happy with the zig template and the zig DX, compared to trying make a platform from scratch in C as my "hello world" LOL. i don't mind a little less magic at this stage.
I literally could not have done it without extensive help from Claude code (z.ai models, cheaper) and a little bit of Gemini CLI (free)
On of the assumptions we have for the future is that platforms will be abundant and should be easy to fork and modify to meet individual needs. It's good to know that LLM's can help with that and the tools should only make that even easier I think. :smiley:
re: justfile, one thing I do sometimes is add one for just myself (like a script for installing), and then put it in .git/info/exclude. In the case of the roc repo, they already ignore extra files with no extensions, so you just need to be careful to not delete it. I think just also has some walk-up-the-tree feature that would let you keep your personal scripts for a repo outside the repo (or you could symlink it)
the way I have decided to structure my project for now, is I have examples/ and test/; I have test/host.roc and test/host.zig, targeting platform/Host.roc and platform/host.zig respectively. I can use the roc and zig test runners, but I am using Just as "my metalayer" to tie everything together and support "complex workflows" like edit-build-test (heh)
for a scripts-on-runtime-person, i am used to executing things immediately, and i don't automatically know to e.g. zig build test but Just helps me with that
also, I think zig has a feature, or is this roc?, where you put the tests in the implementation file, at the toplevel. maybe I should use that more?
to answer my question, zig supports inline tests at the toplevel, but these have a restricted "scope" -- they should be used like unit tests for pure-ish functions defined within the file.
roc supports inline tests at the toplevel, what I am calling comptime tests. these use the expect keyword invoked by roc test. or you can make a "test script" that calls expect within a main function. these are runtime tests invoked by roc.
Is this accurate?
Separately, all host functions are impure by definition, and pure functions cannot call impure functions. so if my application is purely functional, then I cannot call any platform functions?
I know I have a misunderstanding somewhere, just not sure where
expect at the top level is a unit test - these are run with roc test
expect within an expression (like a block expression) is an assertion - these throw a runtime error if not true.
this confirms, but gives me better language. unit test vs assertion
important detail: unlike assertions, expect never stops execution
it will cause a test to fail, and it will report an error, but program execution always continues normally no matter what happens with them
The platform host has a special handler it provides Roc roc_expect_failed or similar which is called when these fail right?
So the platform author is responsible for deciding what to do with that
true, but in --optimize builds the expects are removed, so the semantics of that function are supposed to match - just report, don't end the program
ah, good point. in a test suite, keep going. but a failed runtime assertion should halt. so expect is not ideal in a "runtime test suite"
Still wondering, if all host functions are impure by definition, and pure functions cannot call impure functions, so if my application is purely functional, then I cannot call any platform functions? This doesn't seem quite right.
so expect is not ideal in a "runtime test suite"
yeah, if you want the program to halt, that's what crash is for
oh, good clarification. runtime expect throws an error (which would interrupt a typical test suite) but does not automatically halt
it doesn't interrupt the test suite, it adds to a list of failed expectations
and then at the end of the suite, they all get aggregated and reported together
that's only the comptime expect at the toplevel, as executed by roc test, right? and what i'm calling runtime expect, within a block, would throw a runtime error if it fails, and this error would typically interrupt a naive test suite that is just a series of assertions. that is: a test suite which is not prepared to handle the error thrown by expect. or would such a test suite be forced to handle the error? i'm still trying to figure this stuff out but I'm sure it will become obvious to me with more usage
the number one rule of expect is that it never affects control flow in any way
no matter where it is used
it is a harmless report
maybe a better way to think of it is "debug print that an expectation was false, and then mark this test as 'must fail' when it finishes running"
Thanks! Big fan of the podcast, and I made my introduction here about a year ago, but my focus shifted. Glad to be back
zig4lyfe
That would be cool for a tattoo :p
Fun fact: a former contributor has a tattoo of the Roc logo ![]()
what are some guidelines for "platform modules" under roc-nightly? I know that they must contain at least one Type with the same name. I was going to make platform/Crypto.roc with nominal tag unions: SecretKey PublicKey Signature Digest but this isn't allowed
I have a strange(?) question...
Most of the best functional workflows I've ever used were for Data science, and I'm seriously looking into how to get e.g. Arrow, BLAS, etc. bindings working in Roc.
My understanding right now is that those kinds of I/O would be done via a platform, right? So there might be an "arrow" platform, or a "BLAS" platform. But the problem I'm running into is ... I would never want to use _only_ BLAS, or _only_ Arrow, rather I would use them to accomplish something in the context of another platform (e.g. a basic-cli tool)
But I'm seeing the documentation tutorial says every app has "exactly one platform". What is considered "one platform"? In a declaration like:
app [main!] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br" }
is the platform only "cli" or is it the whole record? Can I have multiple keys in the platform declaration record?
my take, as newbie but also a platform developer, is that you would incorporate all features into the platform. you can have a "BLAS" module or "arrow" module, and maybe a platform specific to each. but if you want both, you can have both, if you work to make them live together which is usually not difficult
what is exactly considered one platform? basic-cli is an amazing resource. it has way more features than I would imagine for a basic-cli, but it also stays simple and approachable
clone this https://github.com/roc-lang/basic-cli and poke around
it looks like you are targeting the documented Roc, which I would call roc-alpha-4, aka the "old Rust compiler". Keep in mind, there is a "new Zig compiler" and the language is changing; I personally target roc-nightly, based on the new compiler (and it's a different language).
Ah, but then how would someone else reuse my "blas platform" code? They would have to build their own platform that combines my BLAS I/O with their own platform?
so BLAS and LAPACK have been two longstanding questions in this area - but this is the first I'm hearing of Arrow! (I know almost nothing about data science; I only know about these libraries because of people asking about them in the context of Roc! :smile:)
@Rachael Sexton I think the running hypothesis is that platforms will evolve around a domain. So if you are working on a particular kind of software, and having these kind of libraries is necessary for e.g. performance reasons, then I would expect the platform author to think of a good API to integrate those. However I'm not sure they will be exposing the functionality 1-1 with those library APIs but maybe wrapping them in a more ergonomic or idiomatic way. So under the hood a platform may be using a particular library but this isn't a detail an application author would be concerned with.
A data science platform sounds like the way to go.
that's probably the way to go for now
personally I think the most promising long-term direction is using an LLM to port BLAS/LAPACK/etc. to pure Roc
they're all pure functions and our (currently non-SIMD) numeric code can optimize to the same things that LLVM optimizes to
what are some guidelines for "platform modules" using the new compiler? I know that they must contain at least one type with the same name. I was going to make platform/Crypto.roc with nominal tag unions: SecretKey PublicKey Signature Digest but this isn't allowed
If I just named it Signature.roc would that be expected to work? I am not sure about visibility / scoping. I ran in to not being able to resolve e.g. Digest from a test file
Let me take a moment to try some stuff
Richard Feldman said:
personally I think the most promising long-term direction is using an LLM to port BLAS/LAPACK/etc. to pure Roc
Interesting. So, what about cases where a lightweight c library exists already, is it idiomatic for module/library authors to depend on c functions right now? E.g. nano arrow should make porting arrow to roc very easy, if so.
Similarly, I wouldn't want to reimplement e.g. pola.rs in roc myself, but it is a rust library that already gets used in node, r, python, and I believe available natively in Nushell
The platform can depend on C functions, Luke's raylib is basically a complex demonstration of how you can set something like that up.
@Anton (platform module resolution) https://gist.github.com/rickhull/68f3fbf668db9f61598628b29ba0feb0
Thanks Rick, I will try to make that work or make an issue
oh whoops, you'll need Signature.roc; updating UPDATED
My presumption is that I'm Doing It Wrong (tm)
Similarly, I wouldn't want to reimplement e.g. pola.rs in roc myself, but it is a rust library that already gets used in node, r, python, and I believe available natively in Nushell
I think pola.rs is a good model for what I'm talking about - the pola.rs folks didn't do FFI to the C code underlying the already-popular pandas, they built a new library in pure Rust :smile:
building new things can be done, and I think there's a lot of precedent for it being the way to get the best outcome!
To further this idea, I am writing nostr-platform, mainly so I can build on top of libsecp256k1. I could implement Schnorr signatures in Roc -- I've already done it in Ruby, maybe the repo I am most proud of, done entirely without LLM assistance agents or code. (I used a lot of LLM assistance for understanding elliptic curve crypto. See the README.md -- it's great, and written by me!)
But in this case, it makes much more sense to rely on a battle-tested lib (Bitcoin!) for important crypto stuff.
for important crypto stuff.
Yeah in that case you want to use the original lib indeed
@Anton for the platform module, I can access Signature.Digest but not Digest. so the module acts as a namespace. I would like to name it Crypto.roc and I'd be fine with Crypto.Signature but I have to expose a "Crypto type". is this a misfeature?
No, I think that is as intended
So if I just want a Crypto namespace, what should a trivial Crypto type look like? or maybe I will have a real type? for what?
alternatively, I could have Digest.roc PublicKey.roc etc. that seems fine, if maybe a bit chatty. Is that more along the intent of platform modules?
For the trivial type you can do Crypto := [].{}
@Richard Feldman may have useful thoughts here
with more testing, I am thinking I should have these nominal tag unions be their own module. it just seems like less friction. but i am continuing to test.
https://gist.github.com/rickhull/c5f63e552465180f4e3c76b99e460f57 (generated via: ./rocgist.sh test/signature.roc platform/Crypto.roc)
this tells me that the trivial type as namespace is flawed. i am going to put the nominal tag unions as their own modules and move on
Anton said:
For the trivial type you can do
Crypto := [].{}
We had a builtin named the same, though I guess now these crypto stuff would just live under Builtin now so that namespace is free to use if people want to roll thier own.
We decided that most of the core cryptographic algorithms should ge implemented by Roc so we can garuntee they are secure and avoid timing attacks. At least that is what I recall, I would need to dig out the zulip thread to refresh myself. We haven't implemented any crypto in the new builtins yet.
See https://github.com/roc-lang/roc/pull/6977
I dont see any issue with implementing algorthims in pure Roc for learning etc, but just wanted to mention this as we haven't talked about it in a while.
Interesting. I have rolled my own crypto systems before, but always for test, not production, stuff like 3DES EDE-CBC; not a crypto engineer; I implemented schnorr sig as an exercise in learning elliptic curve crypto and to enable a pure ruby nostr impl. I don't think I was hitting a name clash with my Roc attempts. I have now abandoned the name "Crypto" in my current platform impl.
I am working on ZIG_PLATFORM_GUIDE.md. This is a different document than what was here, earlier. This has a better starting basis, written mostly by me (!), and will be augmented.
I am using claude's help, and this is a little bit of the blind leading the blind, so please highlight any mistakes, errors, misconceptions, or needed fixes.
@Luke Boswell ^^ if you get a chance
It's very meh... there is some detail that is mostly right, and others that is overly specific to the zig template.
I wouldn't want to share something like that as an authoritative guide or anything as it would probably mostly confuse people
Regarding platform documentation -- I think we are intentionally leaving this undocumented for now.
There are higher priorities, and platform development requires a much higher level of knowledge and experience than application development.
There are many details that are still being validated with the new calling convention. I think it should be stable but there are still a lot of things that just haven't been built yet and so some things may need to change
Good to know. I am basically documenting what I learned about the Zig template. it sounds like basic-cli can be used now? I didn't totally realize that -- it just needs building from source?
E.g. we probably need to sort out the Windows linking story -- but that is hard when we don't have any super experienced Windows devs contributing right now, so it just takes a long time to do anything related with that. I take bites at it every now and then because I'd love to see it happen (for the Windows developers out there) but it can be a slog
Rick Hull said:
Good to know. I am basically documenting what I learned about the Zig template. it sounds like
basic-clican be used now? I didn't totally realize that -- it just needs building from source?
Yeah it's working nicely
i want to get basic-cli going now -- what's step 1, just poke around the repo HEAD?
I have a windows box, and if there is a way to script builds on windows, i can poke around a bit. but i'm guessing it would be a huge PITA to get a semblance of a build environment going on a typical windows desktop
Clone https://github.com/roc-lang/basic-cli then git checkout migrate-zig-compiler then ./build.sh
You will then be able to run the examples using the platform locally
Rick Hull said:
I have a windows box, and if there is a way to script builds on windows, i can poke around a bit. but i'm guessing it would be a huge PITA to get a semblance of a build environment going on a typical windows desktop
It's not a huge PITA or anything... it's just that it requires a fairly deep level of expertise in linking and low level things. Not an area the LLMs are able to provide much assistance... it feels more like voodoo science than engineering
that's like taking the One Ring from gollum :rolling_on_the_floor_laughing:
I've reframed that doc as ZIG_PLATFORM_GUIDE.md and it lives in roc-init for now. I think it's ok to have "rough guides" that are not official docs to be relied upon
Luke Boswell said:
Clone
https://github.com/roc-lang/basic-clithengit checkout migrate-zig-compilerthen./build.sh
I've been working on my own branch, didn't realize this already existed lol
I would say the basic-cli migration is like 3/4 done -- all the hard stuff is behind us, we have resolved all the known bugs that were holding it back now, and we have CI working for all the supported targets. It's only got these modules implemented Cmd, Dir, Env, File, Path, Random, Sleep, Stdin, Stdout, Stderr, Utc so missing things like Http etc... and the implementation of the se modules is definitely a WIP as we need to do a few passes to make these more idiomatic with the new static dispatch approach.
Richard Feldman said:
they're all pure functions and our (currently non-SIMD) numeric code can optimize to the same things that LLVM optimizes to
Here is the kind of code that needs to be written in Mojo to write efficient matmul
https://github.com/jon-chuang/mojo/blob/422256e2490a1899b204e7b3a9cae2c6007433e8/examples/matmul.mojo
It is all about tiling SIMD vectors and doing ops on them. The best tiling strategy depends on the matrix dimensions (which is usually only known at runtime) and the host architecture. Numpy dispatches to the best kernel at runtime. Mojo compiles the exact best kernel only through comptime parametrization. Python using Mojo would JIT compile specialized kernels for the current matrices.
Even then you don't exactly meet numpy(OpenBLAS) performance. https://github.com/modular/modular/pull/3248 (NB: unreliable benchmarking).
These are the kind of challenges that roc would have to enable to reimplement BLAS/LAPACK libraries
whoa, this is super helpful @Romain Lepert , thank you so much! :star_struck:
Romain Lepert said:
The best tiling strategy depends on the matrix dimensions (which is usually only known at runtime) and the host architecture. Numpy dispatches to the best kernel at runtime. Mojo compiles the exact best kernel only through comptime parametrization. Python using Mojo would JIT compile specialized kernels for the current matrices.
just to check my understanding: the "kernel" in this case refers to the tiling strategy?
I think from a comptime perspective we'd be on par with mojo, since if the matrices are statically known, we'd do the dispatch at comptime too (since these are all pure functions).
that said, I don't know if any of the tricks they used to get 3x the perf of numpy (with the extreme caveat that the numpy benchmarks they're comparing to have been really inconsistent, and an earlier run would have had the reverse outcome - numpy being 3x the perf of mojo instead of the other way around) require using memory-unsafe APIs, which would be the main concern I'd have about achieving perf parity there
cc @Brendan Hansknecht
we don't have any SIMD APIs currently, and a major reason for that is my not knowing what to target :laughing:
but if we had a realistic target of "be able to compete with numpy using only memory-safe pure Roc APIs" that would be excellent for unblocking that design work! :smiley:
Haha, that GitHub diff is so useless. Looks to be showing lots of random changes from main rather than just the example changes
Yeah, so what is missing for roc to do that:
isn't loop unrolling also a convenience?
Sadly not. It can be fundamental for performance
Of course when everything goes right llvm can do it.
But sometimes you need to fully unroll or even partially unroll to get better utilization. Balancing code bloat/instruction cache size vs number of branch checks (even if fully predicted it can cause issues)
regarding comptime type parameters with arithmetic - I'm surprised that would be considered a requirement for ergonomics. Lots of languages support these use cases from a perf perspective, but which ones actually have that feature? :sweat_smile:
is it that dynamic languages like Python don't need to care about the static type implications of the APIs, so those ergonomics are out of scope?
In Mojo you write
fn matmul[M: Int, N: Int, K: Int](a: Tensor, b: Tensor, c: Tensor)
and inside you might need to do some arithmetic on the M, N, K comptime type parameters
M / tileM % vectorWidthceilDiv(M, blockSize)min(M, 128)in C++ you don't have comptime type parameters so you have to use template for that which has a lot worse development ergonomics
template<int M, int N, int K>
void matmul(Tensor&, Tensor&, Tensor&);
in python you don't care because you delegate to a precompiled library that would look like this in C++
void matmul(Tensor& a, Tensor& b, Tensor& c)
{
const int M = a.shape[0];
const int N = b.shape[1];
const int K = a.shape[1];
// Example: a small hand-picked set of fast kernels
if (M == 128 && N == 64 && K == 256) {
matmul<128, 64, 256>(a, b, c);
return;
}
if (M == 256 && N == 64 && K == 256) {
matmul<256, 64, 256>(a, b, c);
return;
}
if (M == 128 && N == 128 && K == 256) {
matmul<128, 128, 256>(a, b, c);
return;
}
// fallback
matmul_generic(a, b, c);
}
Yeah, Romain has the right basis. Fundamentally, when python calls the high performance libaries, the first step is to dispatch based on the shape to an optimized kernel for the shape. When you do this, you end up having N different kernels for various kinds of shapes. This is the standard way to deal with dynamism. It actually limits reaching peak performance in some case.
If you are to go to the very extreme like is seen with max graphs:
I don't expect roc to compete in this space. I don't think it needs to in general. That sad, I do think it should be able to compete with numpy or blas or lapack with the right user guiding it. Those libraries tend to have hand rolled loops but minimal extreme specialization. They tend to still treat shapes fully dynamically.
Last updated: Feb 20 2026 at 12:27 UTC