Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions manual/src/reference/types/option.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,22 @@ let my_list = [1,2,3];
let fst = my_list.first?; // Some(1)
```

`get?` retrieves a list element by index, returning `None` instead of erroring when
the index is out of bounds. Unlike the `[]` operator, a negative index does not wrap
around — it is simply out of bounds:

```ndc
let my_list = [10, 20, 30];
my_list.get?(0); // Some(10)
my_list.get?(5); // None
my_list.get?(-1); // None
```

`unwrap_or` extracts the contained value, falling back to a default when the option is `None`:

```ndc
[10, 20, 30].get?(1).unwrap_or(0); // 20
[10, 20, 30].get?(9).unwrap_or(0); // 0
```

> **Note:** unfortunately the language doesn't support pattern matching on options
13 changes: 13 additions & 0 deletions ndc_stdlib/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,19 @@ mod inner {
list.remove(0)
}

/// Returns a copy of the element at `index`, or `None` if the index is out of bounds.
/// A negative index is always out of bounds.
#[function(name = "get?", return_type = Option<_>)]
pub fn maybe_get(list: &[Value], index: i64) -> Value {
Comment thread
timfennis marked this conversation as resolved.
let Ok(idx) = usize::try_from(index) else {
return Value::None;
};
match list.get(idx) {
None => Value::None,
Some(v) => Value::Object(Rc::new(Object::Some(v.clone()))),
}
}

/// Creates a copy of the list with its elements in reverse order
#[function(return_type = Vec<_>)]
pub fn reversed(list: &[Value]) -> Value {
Expand Down
12 changes: 12 additions & 0 deletions ndc_stdlib/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ mod inner {
}
}

/// Extracts the value from an Option, returning `default` when it is `None`.
pub fn unwrap_or(value: Value, default: Value) -> anyhow::Result<Value> {
match value {
Value::Object(obj) => match obj.as_ref() {
Object::Some(inner) => Ok(inner.clone()),
_ => Err(anyhow::anyhow!("incorrect argument to unwrap_or")),
},
Value::None => Ok(default),
_ => Err(anyhow::anyhow!("incorrect argument to unwrap_or")),
}
}

/// Returns a shallow copy of the given value.
pub fn clone(value: Value) -> Value {
value.shallow_clone()
Expand Down
18 changes: 18 additions & 0 deletions tests/functional/programs/601_stdlib_list/004_list_get.ndc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
let list = [10, 20, 30];

// in-bounds returns Some
assert_eq(list.get?(0), Some(10));
assert_eq(list.get?(2), Some(30));

// out of bounds returns None
assert_eq(list.get?(3), None);

// negative indices are always out of bounds (no wrap-around)
assert_eq(list.get?(-1), None);

// empty list
assert_eq([].get?(0), None);

// unwrap_or extracts or falls back to the default
assert_eq(list.get?(1).unwrap_or(99), 20);
assert_eq(list.get?(5).unwrap_or(99), 99);
Loading