Skip to content

Commit

Permalink
Add readme
Browse files Browse the repository at this point in the history
  • Loading branch information
quelgar committed Jan 1, 2024
1 parent cd5dfdf commit f9f3008
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 1 deletion.
104 changes: 104 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Scala Native bindings for libuv

**scala-uv** is a Scala Native library that provides Scala bindings for [libuv](https://libuv.org), which is a multi-platform asynchronous IO library written in C. libuv was originally developed for Node.js, but it's also used by other software projects.

Only Scala 3 is supported at this time.

## Getting it

Not published yet.

## Current status

Very early days, only some of the APIs are bound. What works so far:

* Error handling
* Event loop
* Async callbacks
* Read and write files
* TCP client and server

But the API is still in flux.

## Examples

### Async callback

Runs a callback once, then closes the handle:

```scala
import scala.scalanative.unsafe.*
import scala.scalanative.unsigned.*
import scalauv.*
import LibUv.*

object Main {

private var done = false

private val callback: AsyncCallback = { (handle: AsyncHandle) =>
println("Callback!!")
done = true
uv_close(handle, null)
}

def main(args: Array[String]): Unit = {

withZone {
val loop = uv_default_loop()

val asyncHandle = UvUtils.zoneAllocateHandle(HandleType.UV_ASYNC)
uv_async_init(loop, asyncHandle, callback).checkErrorThrowIO()

uv_async_send(asyncHandle).checkErrorThrowIO()

println(s"Main before, done = $done")
uv_run(loop, RunMode.DEFAULT).checkErrorThrowIO()
println(s"Main after, done = $done")
}

}

}
```

### Test examples

See also the tests

* [TcpSpec.scala](src/test/scala/scalauv/TcpSpec.scala)
* [FileSpec.scala](src/test/scala/scalauv/FileSpec.scala)

## Conveniences

The `LibUv` objects provides the exact libuv API, but when using it directly you are basically writing C code with Scala syntax. A few convenienves are provided to make this less painful.

### Dealing with libuv failures

libuv functions that can fail return a negative integer on failure, with the value indicating the precise error. The possible error codes are in [errors.scala](src/main/scala/scalauv/errors.scala).

Pass an error code to `UvUtils.errorMessage` to get the human-readable error message as a Scala string.

Use `.checkErrorThrowIO()` on the result of a libuv function to throw an `IOException` if the function failed. Note this isn't useful inside a callback, since you definitely should *not* throw exceptions from a C callback.

```scala
uv_listen(serverTcpHandle, 128, onNewConnection).checkErrorThrowIO()
```

Use `.onFail` to run some cleanup if the function failed.

```scala
uv_write(writeReq, stream, buf, 1.toUInt, onWrite).onFail {
stdlib.free(writeReq)
}
```

### Malloc C strings

While the `Zone` memory allocation API from Scala Native is very nice, it's not useful when the memory is freed in a different callback, as there won't be a shared lexical scope. So `mallocCString` converts a Scala string to a C string, allocating the memory the old-fashioned way.

### Other UvUtils methods

* allocate memory for requests
* allocate memory for handles
* allocate, use and free file system requests
1 change: 0 additions & 1 deletion src/main/scala/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import scala.scalanative.*
import unsafe.*
import unsigned.*
import LibUv.*
import UvUtils.*

final class Test {

Expand Down
7 changes: 7 additions & 0 deletions src/main/scala/scalauv/UvUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ object UvUtils {

object FsReq {

/** Allocate a new FS request, provide it to the specified function, and
* clean it up after the function returns. Note this is only safe to use
* for **blocking** FS operations.
*
* @param f
* A function that performs **blocking** FS operations.
*/
inline def use[A](inline f: Req => A): A = {
val req = stackAllocateRequest(RequestType.FS)
try f(req)
Expand Down

0 comments on commit f9f3008

Please sign in to comment.