Lower Typed Jerboa option results to Rust

ober

9c3d4900f482ba163f23682967b689551f0654a2

diff --git a/docs/jerboa-to-rust.md b/docs/jerboa-to-rust.md
index 932577a..4489710 100644
--- a/docs/jerboa-to-rust.md
+++ b/docs/jerboa-to-rust.md
@@ -154,10 +154,9 @@ Initial type mapping:
 | Record | `struct` |
 | Variant | `enum` |
 
-Current landing: the checker types explicit Option/Result constructors:
-`option-some`, `option-none`, `result-ok`, and `result-err`. The Rust backend
-already maps Option/Result types, but constructor expression lowering is still
-future work.
+Current landing: the checker types explicit Option/Result constructors and the
+Rust backend lowers them: `option-some` -> `Some(...)`, `option-none` -> `None`,
+`result-ok` -> `Ok(...)`, and `result-err` -> `Err(...)`.
 
 Boundary-facing values should use ABI-safe wrappers instead of raw Rust layout.
 
diff --git a/docs/typed-jerboa.md b/docs/typed-jerboa.md
index 1042b4d..7f84e51 100644
--- a/docs/typed-jerboa.md
+++ b/docs/typed-jerboa.md
@@ -212,7 +212,8 @@ Current landing:
 - `Option` and `Result` type expressions now have explicit checked
   constructors in the front end: `(option-some expr)`, `(option-none Type)`,
   `(result-ok expr ErrorType)`, and `(result-err ValueType expr)`. Rust
-  lowering for these constructors is still future work.
+  lowering maps them to `Some`, `None`, `Ok`, and `Err` without treating type
+  operands as runtime values.
 - The current Rust lowering uses a conservative Clone-only ownership model:
   generated constructors, ordinary calls, and recursive match rebinding clone
   owned values so recursive branch code can pass the same child value to more
diff --git a/lib/jerboa/typed/rust.ss b/lib/jerboa/typed/rust.ss
index f687ecc..b6e50b9 100644
--- a/lib/jerboa/typed/rust.ss
+++ b/lib/jerboa/typed/rust.ss
@@ -911,6 +911,26 @@
       (emit-expression (car args))
       ").len() as u64"))
 
+  (def (emit-option-some args)
+    (unless (= (length args) 1)
+      (error 'typed-rust "option-some expects one operand" args))
+    (string-append "Some(" (emit-argument-expression (car args)) ")"))
+
+  (def (emit-option-none args)
+    (unless (= (length args) 1)
+      (error 'typed-rust "option-none expects one type operand" args))
+    "None")
+
+  (def (emit-result-ok args)
+    (unless (= (length args) 2)
+      (error 'typed-rust "result-ok expects value and error type operands" args))
+    (string-append "Ok(" (emit-argument-expression (car args)) ")"))
+
+  (def (emit-result-err args)
+    (unless (= (length args) 2)
+      (error 'typed-rust "result-err expects value type and error operands" args))
+    (string-append "Err(" (emit-argument-expression (cadr args)) ")"))
+
   (def (emit-call name args)
     (let ([record-constructor (lookup-record-constructor name)]
           [record-accessor (lookup-record-accessor name)]
@@ -932,6 +952,14 @@
          (emit-string-append args)]
         [(eq? name 'bytevector-length)
          (emit-bytevector-length args)]
+        [(eq? name 'option-some)
+         (emit-option-some args)]
+        [(eq? name 'option-none)
+         (emit-option-none args)]
+        [(eq? name 'result-ok)
+         (emit-result-ok args)]
+        [(eq? name 'result-err)
+         (emit-result-err args)]
         [else
          (string-append
            (rust-symbol-name name)
diff --git a/tests/test-typed-rust.ss b/tests/test-typed-rust.ss
index afd2e49..d420740 100644
--- a/tests/test-typed-rust.ss
+++ b/tests/test-typed-rust.ss
@@ -169,6 +169,21 @@
 (define handle-rust
   (typed-library-form->rust-string handle-form))
 
+(define option-result-form
+  '(typed-library (sample typed option-result)
+     (export maybe-value none-text ok-value err-value)
+     (def (maybe-value (x : Nat)) : (Option Nat)
+       (option-some x))
+     (def (none-text) : (Option String)
+       (option-none String))
+     (def (ok-value (x : Nat)) : (Result Nat String)
+       (result-ok x String))
+     (def (err-value (message : String)) : (Result Nat String)
+       (result-err Nat message))))
+
+(define option-result-rust
+  (typed-library-form->rust-string option-result-form))
+
 (printf "--- Typed Jerboa Rust emitter tests ---~%")
 
 (test "rust symbol sanitizes"
@@ -264,6 +279,17 @@
        (substring? handle-rust "let token = jt_clone_handle::<Token>(token_handle).unwrap_or_else(|| panic!(\"invalid typed handle\"));"))
   #t)
 
+(test "rust lowers option and result constructors"
+  (and (substring? option-result-rust "pub fn maybe_value(x: u64) -> Option<u64>")
+       (substring? option-result-rust "Some((x).clone())")
+       (substring? option-result-rust "pub fn none_text() -> Option<String>")
+       (substring? option-result-rust "None")
+       (substring? option-result-rust "pub fn ok_value(x: u64) -> Result<u64, String>")
+       (substring? option-result-rust "Ok((x).clone())")
+       (substring? option-result-rust "pub fn err_value(message: String) -> Result<u64, String>")
+       (substring? option-result-rust "Err((message).clone())"))
+  #t)
+
 (printf "~%Typed Rust emitter: ~a passed, ~a failed~%" pass fail)
 (when (> fail 0)
   (exit 1))