Browse the docs

silk.core.option.

Optional owned values that are present or absent, with no explanation for the absence.

When to use#

Use Option when "no value" is a normal answer, such as a lookup that finds nothing. Use silk.core.result.Result when the caller must know why the value is missing.

Use map to transform a present value, flatMap when the next step can also produce no value, and unwrapOr to get a plain value with a fallback. Use match when the present and absent cases need different custom behavior.

Details#

Option<T> is a union with the variants Some and None. Each combinator consumes the option. A present value moves into the result. A value that the operation does not use is dropped. No combinator copies T, so T does not need to be Copy.

Examples#

Look up a port and fall back to a default#

silk
import silk.core.option { Option }

fn portFor(scheme: string) -> Option<i32> {
  if scheme == "http" {
    return Option<i32>.some(80)
  }
  return Option<i32>.none()
}

fn secure(port: i32) -> Option<i32> {
  if port == 80 {
    return Option<i32>.some(443)
  }
  return Option<i32>.none()
}

pub fn main() -> i32 {
  let known = portFor("http") |> Option<i32>.flatMap<i32>(secure) |> Option<i32>.unwrapOr(8080)
  let unknown = portFor("gopher") |> Option<i32>.flatMap<i32>(secure) |> Option<i32>.unwrapOr(8080)
  if known != 443 || unknown != 8080 {
    return 1
  }
  return 0
}

Make a service optional#

An effect that requires &Option<&S> runs with or without a provider of S. See withSome.

silk
import silk.core.effect { Effect }
import silk.core.option { Option, provideSome, withSome }

service Tracer {
  effect fn trace(value: i32) -> i32 ? &Tracer
}

struct Offset {
  amount: i32,
}

impl Tracer for Offset {
  effect fn trace(self: &Self, value: i32) -> i32 {
    return value + self.amount
  }
}

effect fn plain() -> i32 {
  return 1
}

effect fn traced() -> i32 ? &Tracer {
  return run Tracer.trace(1)
}

effect fn measure() -> i32 ? &Option<&Tracer> {
  return run withSome<&Tracer>(plain(), traced())
}

pub fn main() -> i32 {
  let offset = Offset {amount: 10}
  let none = Option<never>.none()
  let present = run provideSome<&Tracer>(measure(), &offset)
  let absent = run Effect.provide(measure(), &none)
  if present != 11 || absent != 1 {
    return 1
  }
  return 0
}

Import as Option with import silk.core.option { Option }.

Public declarations: 5.

Option#

silk
pub union Option<T>

An owned value of type T that is present (Some) or absent (None).

When to use#

Use Option as a return type when "no value" is a normal result and needs no reason. Use silk.core.result.Result when the caller must know why the value is missing.

Details#

Create values with some and none. Test them with isSome and isNone. Use map, flatMap, and unwrapOr for the common cases. Use match when the two cases need different custom behavior. The option owns its value, so dropping a present option also drops the value.

In a requirement row, &Option<&S> is an optional service: the effect runs with or without a provider of S. Branch on it with withSome.

Examples#

Handle both cases with match#

silk
import silk.core.option { Option }

fn describe(found: Option<i32>) -> i32 {
  return match move found {
    Option<i32>.Some { value } => value
    Option<i32>.None => -1
  }
}

pub fn main() -> i32 {
  let found = describe(Option<i32>.some(3))
  let missing = describe(Option<i32>.none())
  if found != 3 || missing != -1 {
    return 1
  }
  return 0
}

None#

silk
None

The absent case. It carries no value and no reason for the absence.

Some#

silk
Some

The present case. It owns one value of type T.

Field value#

silk
pub value: T

The owned present value.

Implementation Copy for Option<T>#

silk
impl<T: Copy> Copy for Option<T>

An Option of a Copy type is Copy, so a struct with an Option field can be Copy too.

Implementation Option<T>#

silk
impl<T> Option<T>

Associated function none#

silk
pub fn none() -> Option<T>

Returns an absent Option<T>.

Details#

The result holds no value and allocates no memory. Write the element type explicitly, as in Option<i32>.none(), because no argument gives the compiler that type.

Associated function some#

silk
pub fn some(value: T) -> Option<T>

Returns a present Option<T> that owns value.

Details#

This function moves value into the option. The caller can no longer use the original binding.

Method isSome#

silk
pub fn isSome(self: &Self) -> bool

Returns true if self holds a value.

When to use#

Use isSome when only the presence matters. Use match or unwrapOr to read the value.

Details#

This function borrows self, so the option and its value stay available.

Examples#

Test an option and keep it#

silk
import silk.core.option { Option }

pub fn main() -> i32 {
  let found = Option<i32>.some(3)
  if !Option<i32>.isSome(&found) {
    return 1
  }
  let value = move found |> Option<i32>.unwrapOr(0)
  return value - 3
}

Method isNone#

silk
pub fn isNone(self: &Self) -> bool

Returns true if self holds no value.

When to use#

Use isNone when only the absence matters, such as to stop a search early. Use match or unwrapOr to read a present value.

Details#

This function borrows self, so the option and its value stay available.

Examples#

Test an absent option#

silk
import silk.core.option { Option }

pub fn main() -> i32 {
  let missing = Option<i32>.none()
  if !Option<i32>.isNone(&missing) {
    return 1
  }
  return 0
}

Method map#

silk
pub fn map<U>(self: Self, transform: once fn(T) -> U) -> Option<U>

Returns transform(value) as a present option if self is present. Returns an absent option if self is absent.

When to use#

Use map when the transform always succeeds. Use flatMap when the transform can also produce no value.

Details#

transform runs one time for a present value and does not run for an absent value. This function consumes self and moves the present value into transform.

Gotchas#

The original option is not available after the call. To read the value and keep the option, use match on a borrow of the option.

Examples#

Double a present value#

silk
import silk.core.option { Option }

fn double(value: i32) -> i32 {
  return value * 2
}

pub fn main() -> i32 {
  let doubled = Option<i32>.some(21) |> Option<i32>.map<i32>(double) |> Option<i32>.unwrapOr(0)
  if doubled != 42 {
    return 1
  }
  return 0
}

Method flatMap#

silk
pub fn flatMap<U>(self: Self, transform: once fn(T) -> Option<U>) -> Option<U>

Returns the option that transform produces for a present value. Returns an absent option if self is absent.

When to use#

Use flatMap to chain steps where each step can produce no value. The result is one Option<U>, not a nested Option<Option<U>>. Use map when the step always produces a value.

Details#

transform runs one time for a present value and does not run for an absent value. This function consumes self and moves the present value into transform.

Examples#

Reject a value in the second step#

silk
import silk.core.option { Option }

fn positive(value: i32) -> Option<i32> {
  if value > 0 {
    return Option<i32>.some(value)
  }
  return Option<i32>.none()
}

pub fn main() -> i32 {
  let kept = Option<i32>.some(5) |> Option<i32>.flatMap<i32>(positive) |> Option<i32>.unwrapOr(0)
  let rejected = Option<i32>.some(-5) |> Option<i32>.flatMap<i32>(positive)
    |> Option<i32>.unwrapOr(0)
  if kept != 5 || rejected != 0 {
    return 1
  }
  return 0
}

Method unwrapOr#

silk
pub fn unwrapOr(
    self: Self,
    /// The value to return if `self` is absent. It is dropped if `self` is present.
    fallback: T,
  ) -> T

Returns the present value. Returns fallback if self is absent.

When to use#

Use unwrapOr at the end of an option chain when a default value is correct for the absent case. Use match when the absent case needs different control flow, such as an early return.

Details#

This function consumes self and fallback. It returns one of the two values and drops the other value.

Gotchas#

The caller evaluates fallback before the call, also when self is present. If the fallback is expensive to make, use match and make the fallback only in the absent case.

Examples#

Use a present value and an absent value#

silk
import silk.core.option { Option }

pub fn main() -> i32 {
  let present = Option<i32>.some(7)
  let absent = Option<i32>.none()
  let first = move present |> Option<i32>.unwrapOr(0)
  let second = move absent |> Option<i32>.unwrapOr(5)
  if first != 7 || second != 5 {
    return 1
  }
  return 0
}

Parameter fallback#

silk
fallback: T

The value to return if self is absent. It is dropped if self is present.

OptionService#

silk
pub service OptionService<?S>

The service that an &Option<&S> requirement names.

When to use#

Do not name this service in your code. Write ? &Option<&S> in a requirement row and branch with withSome. Diagnostics name this service where they name the key.

Details#

Option<&S> is an ordinary requirement key, separate from S. An Option<P> provides it when &P provides S: Option<P>.some(provider) is present and Option<P>.None is absent. The explicit absent provider is Option<never>.none(), because never serves every service and has no values. provideSome lifts a borrowed provider of S into a present one.

Operation select#

silk
effect<'env> fn select<'env, A, E, ?R>(
    absent: once Effect<'env; A ! E ? R>,
    present: once Effect<'env; A ! E ? R | S>,
  ) -> A ! E ? R | &OptionService<S>
  where R in Without<R, S>

Runs present with the provider of S when one exists, otherwise runs absent.

Implementation OptionService<S> for Option<P>#

silk
impl<P, ?S> OptionService<S> for Option<P> where &P provides S from S

An Option serves an optional service: Some holds the provider and None has none.

Operation select#

silk
effect<'env> fn select<'env, A, E, ?R>(
    self: &Self,
    absent: once Effect<'env; A ! E ? R>,
    present: once Effect<'env; A ! E ? R | S>,
  ) -> A ! E ? R
  where R in Without<R, S>

Runs present with the provider in Some, or runs absent for None.

withSome#

silk
pub effect fn withSome<'env, ?S, A, E, ?R>(
  absent: once Effect<'env; A ! E ? R>,
  present: once Effect<'env; A ! E ? R | S>,
) -> A ! E ? R | &OptionService<S>
where R in Without<R, S>

Runs present when the optional service S has a provider, otherwise runs absent.

When to use#

Use this function inside an effect that requires &Option<&S>, where the work differs with and without S, for example optional tracing or metrics.

Details#

The type argument is the requirement entry, such as withSome<&Tracer>. The provider decides the branch at runtime with one tag check. The branch that does not run is dropped. Both branches share the other requirements R, which must not contain S, and only present may use S.

Gotchas#

A provider of S does not satisfy &Option<&S>. Provide Option<P>.some(provider), Option<never>.none(), or use provideSome.

Present#

silk
pub struct Present<'env, P>

A borrowed provider that always serves an optional service as present.

Details#

provideSome makes one for a single provision. Use Option<P> instead when the provider is owned or when presence is decided at runtime.

Field provider#

silk
pub provider: &'env P

The borrowed provider of the optional service.

Implementation OptionService<S> for Present<'env, P>#

silk
impl<'env, P, ?S> OptionService<S> for Present<'env, P> where &'env P provides S from S

A Present always runs the present branch with its borrowed provider.

Operation select#

silk
effect<'run> fn select<'run, A, E, ?R>(
    self: &Self,
    absent: once Effect<'run; A ! E ? R>,
    present: once Effect<'run; A ! E ? R | S>,
  ) -> A ! E ? R
  where R in Without<R, S>

Runs present with the borrowed provider and drops absent.

provideSome#

silk
pub effect fn provideSome<'env, ?S, A, P, E, ?R>(
  program: once Effect<'env; A ! E ? R>,
  provider: &'env P,
) -> A ! E ? Without<R, OptionService<S>>
where &Present<'env, P> provides OptionService<S> from R

Provides a borrowed provider of S to the optional requirement &Option<S> of program.

When to use#

Use this function when you have a provider of S and the effect requires &Option<S>. A provider of S does not satisfy &Option<S> without this explicit lift.

Details#

The type argument is the requirement entry, such as provideSome<&Tracer>. The provider stays borrowed for the execution of program. Every other requirement in R stays visible in the result.

Category: Core.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2.

View the source of this page