Repository navigation
Category Optional
Home › All Categories › Optional
An Optional is a box that either holds one value or is empty. It is how a function says "here is your answer -- or there is none" without inventing a special value, such as 0 or -1, to mean nothing.
Think of a parcel locker: when you open it there is either a parcel inside or there is not, and you can tell which before you reach in. An empty locker is never mistaken for a parcel.
You make a full one with Some( value ) and an empty one with None( of T )( ). Whoever receives it asks HasValue( ) before taking the value out with Value( ), or uses ValueOr( fallback ), which gives back the value if there is one and the fallback if not. Because being empty is part of the type, a real value of 0 or -1 can never be confused with "no answer" -- see Zero Is A Value.
Use an Optional for a result that may simply be missing: a lookup that finds nothing, a setting that was never given. When the caller also needs to know why there is no value, use a Result instead.
The example below looks up a year by name. A known name gives a value; an unknown one gives an empty Optional, and ValueOr supplies -1 in its place:
#import "std/outcome"
using outcome
function Lookup( byref key as ustring ) as Optional( of int32 )
if( key = "ada" ) then
return Some( 1815L )
end if
return None( of int32 )( )
end function
dim r as Optional( of int32 ) = Lookup( "ada" )
if( r.HasValue( ) ) then
print r.Value( ) ' 1815
end if
dim as int32 unknown = -1
print Lookup( "nobody" ).ValueOr( unknown ) ' -1Without Optional there is no way to say "a value that might not be there". Absence is encoded as 0, -1, NULL, an empty string, or an out-parameter boolean -- differently in every library, and indistinguishable from a legitimate result whenever the sentinel is itself a legal value:
function Find( ... ) as int32 ' is -1 "absent", or an index?
if( Find( ... ) = -1 ) then
dim idx as int32 = IndexOf( v, x ) ' 0 could mean "found at 0"
' or "not found" -- depends on the APIOptional puts the absence IN THE TYPE, where it has a name and cannot be confused with a value. It also removes the out-parameter form: 'TryGet( key, byref out ) as boolean' is the shape a function takes only when it has no way to return "a long, or nothing".
HasValue( ) as boolean O(1)
Value( ) byref as T O(1) RAISES if empty
ValueOr( byref dflt as T ) byref as T O(1) never raises
Clear( ) O(1) becomes emptyand two FREE PROCEDURES, not constructors:
Some( v ) as Optional( of T ) T inferred from v
None( of T )( ) as Optional( of T ) T must be written outThere is deliberately no operator '=', no comparison, no ToString and no GetIterator. See WHY THERE ARE SO FEW MEMBERS below.
dim o as Optional( of int32 ) ' empty, not "uninitialised"There is no third state to get wrong. Construction by free procedure rather than by constructor is what lets T be inferred:
dim o as Optional( of int32 ) = Some( 3L ) ' not Optional( of long )( 3 )None( ) exists so that returning nothing reads as a decision rather than as a forgotten assignment:
return None( of int32 )( ) ' deliberate
return ' also empty, but says nothingT cannot be inferred for None( ) -- there is no argument to infer it from -- so it has to be written out.
Some( ) infers T from the ARGUMENT, and an untyped integer literal is an INTEGER:
dim o as Optional( of int32 ) = Some( 3 ) ' Optional( of integer ):
' type mismatch
dim o as Optional( of int32 ) = Some( 3L ) ' correctThe same rule the containers already carry, for the same reason: inference reads parameter positions and does not coerce. Use a suffixed literal or a variable of the element type.
Value( ) on an empty Optional raises FB_RTERROR_ILLEGALFUNCTIONCALL through the ordinary runtime-error path. With no handler installed the program stops:
Aborting due to runtime error 1 (illegal function call) at line 92 of ...\pkg\std\outcome\optional.tiko()It is catchable by 'on error' and reports a line under -exx. This is a bug at the call site, not a recoverable condition: HasValue( ) or ValueOr( ) is available and costs nothing.
ValueOr( ) is the branch-free alternative and never raises:
print o.ValueOr( fallback )Both return BYREF. ValueOr( ) therefore aliases either the stored value or the CALLER'S default, so passing a temporary gives a reference that dies at the end of the statement -- the same hazard as any byref return, not an Optional one.
Value( ) IS usable as an assignment target, and writes through to the stored value:
dim o as Optional( of int32 ) = Some( 1L )
o.Value( ) = 5
print o.Value( ) ' 5Note this differs from Array.At( ), where 'a.At( 0 ) = 11' does NOT assign -- measured, both under -w 3, and both silent. Do not assume one from the other. Binding byref is unambiguous and works for both:
dim byref as int32 r = o.Value( )
r = 5This is the whole point, and it is worth stating on its own:
dim engaged as Optional( of int32 ) = Some( 0L )
dim empty_ as Optional( of int32 )
engaged.ValueOr( zero ) ' 0
empty_.ValueOr( zero ) ' 0 -- same answer
engaged.HasValue( ) ' true
empty_.HasValue( ) ' false -- different statesEvery sentinel encoding Optional replaces gets this case wrong.
Not minimalism. Every member of a generic is instantiated whether it is called or not, so a member needing '=' on T would make the WHOLE TYPE unusable for any T without one -- the same rule that forced Array's Sort, IndexOf and Contains to become free procedures.
So Optional's members use only default construction, copy and destruction, and Optional( of T ) instantiates for any T that has those. Including containers, which have no '=':
dim o as Optional( of Array( of int32 ) ) ' fineAnything needing more than that belongs in a free procedure, where the requirement lands on the call that actually needs it.
Optional is a T plus a boolean, not a tagged union. The stored T is always default-constructed, whether or not the Optional is engaged, so:
sizeof( Optional( of T ) ) >= sizeof( T ) + sizeof( boolean )and T's default constructor runs for an empty Optional. That is the price of not having sum types in the language; it also means T MUST be default-constructible:
type NoDefaultCtor
as int32 a
declare constructor( byval n as int32 ) ' suppresses the implicit one
end type
dim o as Optional( of NoDefaultCtor ) ' error 183: TYPE or CLASS
' has no default constructorNo compiler change went into this, and several things were deliberately NOT done:
no '?' operator ' propagating early-return
no must-use diagnostic ' ignoring an Optional stays a convention
no sum type underneath ' see above
no implicit conversion to boolean ' 'if( o ) then' does not compileWhat it buys is that the absence has a name and a type. Nothing more is claimed.
There is no GetIterator( ), so 'for each' over an Optional is a compile error rather than a zero-or-one loop. A maybe-value is not a sequence, and pretending it is makes the absence easy to skip silently:
for each v in maybe ' would do nothing, and say nothing
use( v )
nextWrite the branch.
Copy and assignment are DEEP, because the T is stored by value and tiko's value semantics do the work. 'dim b = a' copies the flag and the T; assignment overwrites both. The two are independent afterwards in BOTH directions.
Clear( ) resets the slot rather than only clearing the flag, so a T holding a resource -- a string, a container -- releases it at the Clear( ) rather than at the Optional's own destruction.
An Optional( of T ptr ) copies the POINTER. There are no smart pointers.
Optional( of Optional( of T ) ) works and has three distinguishable states, which is what makes it composable at all.
Value( ) returns a reference to the stored slot even on the raising path, because a byref return must refer to something and the slot is always a real default-constructed object.
It does NOT use the function-'static' blank that LinkedList.Front( ) uses. That pattern is correct there -- an empty list has no node to point at -- but it would be a leak here: a function-'static' UDT in tiko lives until the program ends, so every instantiation of the member would retain one T, and its resources, for the lifetime of the program.
Source: pkg/std/outcome/optional.tiko
src/tests/generics/container-optional.bas is exhaustive: every member, both states, both free constructors, all four assignment transitions between the states, self-assignment, copy independence in both directions, byref mutation through Value( ) and ValueOr( ), element types long / double / string / a UDT with no operators / a nested Array / a nested Optional, an Optional as a return value, and destructor balance with a counting element type -- asserted for the engaged case and for the never-engaged case separately.
src/tests/generics/optional-empty-value.bas is a COMPILE_AND_RUN_FAIL test pinning that Value( ) on an empty Optional aborts. It is a separate module because the raise ends the process.
src/tests/generics/fail-optional-nodefaultctor.bas pins the diagnostic for a T with no default constructor.
Every entry, grouped by task. Errors and missing values lists them beside the rest of the area.
| Function | What it does | From |
|---|---|---|
| Some | Returns an Optional holding the value, its type taken from the argument. | std/outcome |
| None | Returns an empty Optional of the type you write out. | std/outcome |
| Optional.HasValue | Returns true if the Optional holds a value. | std/outcome |
| Optional.Value | Returns the value by reference; raises an error if the Optional is empty. | std/outcome |
| Optional.ValueOr | Returns the value, or the fallback you pass if the Optional is empty. | std/outcome |
| Optional.Clear | Empties the Optional, releasing whatever its value held. | std/outcome |
tiko-lang · Home · Feature Index · Keyword Index · License
FreeBASIC manual pages reproduced under the GNU FDL 1.3.
- Home
- What Is tiko-lang
- Quick Start
- Installing on macOS
- Debugging
- Feature Index
- Compatibility
- FAQ
- AI Cheatsheet
- Generics
- Interfaces
- For Each
- Iterators
- Tuples
- Array Results
- Defer
- Lambdas
- Block Strings
- Partial Types
- String Slices
- Embed
- Typed Variadics
- C Header Import
- Foreign Library Linking
- UString
- Fixed-Width Types
- Nullptr
- String Library
- Format
- Array
- Map
- Set
- LinkedList
- OrderedMap
- OrderedSet
- Sorting
- Optional
- Result
- Smart Pointers
- Date and Time (Chrono)
- Time Zones
- Filesystem
- File Streams
- Config (INI and TOML)
- Codec
- Hash
- UUID
- OS
- Process
- Net
- HTTP and TLS
- Math Library
- Finance
- Logging
- Terminal TUI Library
- PsControls
- tkControls
- sys/win
- Goroutines and Channels
- LLVM Backend
- tiko build
- TIKO_COMPILER
- Variable Declarations
- Standard Data Types
- User Defined Types
- Procedures
- Operators
- Control Flow Statements
- Converting Data Types
- Array Functions
- String Functions
- Mathematical Functions
- Bit Manipulation
- Date and Time Functions
- File I/O Functions
- Console Functions
- User Input (Runtime Library)
- User Input Functions (Graphics Library)
- Screen Functions
- 2D Drawing Functions
- Graphics Library Reference
- Graphics Keyword List
- Memory Functions
- Threading Support Functions
- Error Handling Functions
- Operating System Functions
- Preprocessor
- Intrinsic Defines
- Miscellaneous Keywords
- Modularizing