This is the first version of the SafeScript language specification.
Creation Date: February 2, 2024 (INDEV)

- Introduction - Introduction to the SafeScript language.
- Safety - The safety features of SafeScript.
- No Nulls / No Null Pointers / No Undefineds - Absence of nulls, null pointers and undefineds.
- No Exceptions - How SafeScript does not have exceptions.
- Unwraping - The use of
unwrap
for bothOption
andResult
.
- Syntax - The syntax of SafeScript.
- Primitives - The primitive types of SafeScript.
- Core - The standard library of SafeScript, also known as
core
.core.prelude
- The re-exported types and functions.
- Implementation Notes - Notes on the implementation of SafeScript.
- FAQ - Frequently asked questions about SafeScript.
SafeScript is a morphic language (yes, I did make that word up). That means that it can be both compiled and interpreted (as well as embedded). The language is designed to be safe, simple and fast. It is a statically typed language with an algebraic data type system (similar to Haskell). It is designed to be a scripting language, that is easily embeddable in other applications.
The language is designed to be simple and easy to learn, as it is based off of TypeScript.
The language is designed to be safe, thus it has no nulls, no undefineds,
no exceptions and no runtime crashes (assuming no use of unwrap
s).
SafeScript is designed to be safe, thus it has no nulls, no undefineds,
no exceptions and no runtime crashes (assuming no use of unwrap
s).
This is achieved through the use of the Option
and Result
types. The use of these types is encouraged, as it makes the code safer and
easier to reason about.
Due to the fact that nulls are the source of many bugs,
SafeScript does not have nulls, undefineds or null pointers.
Instead, it uses the Option
type to represent a value that may or
may not be present.
Example:
let x: Option<num> = Some(5);
let y: Option<num> = None;
SafeScript does not have exceptions,
but that does not mean that it does not have error handling.
Many other languages, have syntax for throwing and catching exceptions.
try
, catch
, throw
, raise
, except
, finally
, etc.
SafeScript does not have any of that.
Instead, SafeScript uses the Result
type to represent a value that
may or may not be an error. Similar to rust, the Result
type has
two variants: Ok
and Err
.
Example:
let x: Result<num, str> = Ok(5);
let y: Result<num, str> = Err("An error occurred");
The use of the unwrap
method is discouraged in production code, or performance
critical code, as it can lead to crashes, and can be slow. However, it is extremely
useful for prototyping, testing and debugging. As it can be used to quickly
figure out what is wrong with the code and where.
Essentially, unwrap
is the only way to cause a runtime crash in SafeScript.
Thus, if you do not use unwrap
, you will not have any runtime crashes.
Obviously, that is not always easy, but it is possible.
SafeScript does not have many primitives, as it is designed to be simple.
The primitives that it does have are: bool
, num
, str
,
char
, and void
. They are all denoted in lowercase for simplicity.
The bool
type is a primitive type that represents a boolean value.
It can be either true
or false
.
Methods:
thenSome<T>(self, value: T) -> Option<T>
- If the boolean istrue
, returnsSome(value)
, otherwise returnsNone
.then<T>(self, f: fn() -> T) -> Option<T>
- If the boolean istrue
, returnsSome
containing the result of invokingf
, otherwise returnsNone
.
Trait Implementations:
core.fmt.Debug
- Formats the boolean as"true"
or"false"
.core.fmt.Display
- Formats the boolean as"true"
or"false"
.core.ops.Not
- Returnstrue
if the boolean isfalse
, andfalse
if the boolean istrue
.core.ops.BitAnd<Ouput = bool>
- Returnstrue
if both booleans aretrue
, andfalse
otherwise.core.ops.BitAndAssign
- Assignsself
toself.bitAnd(rhs)
.core.ops.BitOr<Ouput = bool>
- Returnstrue
if either of the booleans aretrue
, andfalse
otherwise.core.ops.BitOrAssign
- Assignsself
toself.bitOr(rhs)
.core.ops.BitXor<Ouput = bool>
- Returnstrue
if the booleans are different, andfalse
if they are the same.core.ops.BitXorAssign
- Assignsself
toself.bitXor(rhs)
.
Implementation Notes:
- The
bool
type is to be implemented as a single byte, with the value0
representingfalse
, and the value1
representingtrue
. This is to ensure that thebool
type is as efficient as possible. - Implementation is to have no look-up table attached to it.
That means that
thenSome
andthen
are to be implemented as inline functions.
The num
type is a primitive type that represents a number.
It can be an integer or a floating point number, with arbitrary precision.
(I will be using Malachite)
The str
type is a primitive type that represents a sequence of characters.
It is a UTF-8 encoded string. (Although the implementation can vary,
it should NOT be a null-terminated string, for safety reasons.)
The char
type is a primitive type that represents a single character.
It is a Unicode scalar value.
The void
type is a primitive type that represents the absence of a value.
It is similar to the void
type in TypeScript. Or the unit
type in Rust.
The standard library of SafeScript is called core
.
It contains the most basic types and functions.
By default, the core
module is imported into every SafeScript file,
and can be accessed using the core
namespace.
Example:
let x: core.Option<num> = core.Option.Some(5);
However, the most commonly used types and functions are re-exported in the
core.prelude
module, and can be accessed without the core
namespace.
Example:
let a1: Option<num> = Some(5);
let a2: Option<num> = None;
let b1: Result<num, str> = Ok(5);
let b2: Result<num, str> = Err("An error occurred");
let c: str = "Hello, World!";
let d: char = 'a';
let e: bool = true;
let f: void;
The core.prelude
module contains the most commonly used types and functions.
It is re-exported in every SafeScript file, and can be accessed without the core
namespace.
It contains:
The core.eh
contains the error handling types and functions.
Such as Result
and Option
.
As well as the Error
type.
The Result
type is a type that represents a value that may or may not be an error.
This is used all over the place in SafeScript. Especially in IO operations.
This is one of the ways that SafeScript can guarantee safety.
Signature:
enum Result<O, E> {
Ok(O),
Err(E),
}
Generics:
O
- The type of the Ok value.E
- The type of the Err value.
Variants:
Represents a successful value.
Represents an error value.
Methods:
isOk(&self) -> bool
- Returns true if the result is a Ok value.isErr(&self) -> bool
- Returns true if the result is a Err value.ok(self) -> Option<O>
- If the result is a Ok value, returns a Some value with the inner value. If the result is a Err value, returns a None value.err(self) -> Option<E>
- If the result is a Ok value, returns a None value. If the result is a Err value, returns a Some value with the inner value.expect(self, msg: str) -> O
- Returns the inner value of the result. Panics withmsg
if the result is a Err value.expectErr(self, msg: str) -> E
- Returns the inner value of the result. Panics withmsg
if the result is a Ok value.unwrap(self) -> O
- Returns the inner value of the result. Panics if the result is a Err value.unwrapErr(self) -> E
- Returns the inner value of the result. Panics if the result is a Ok value.unwrapOr(self, backup: O) -> O
- Returns the inner value of the result if it is a Ok value. If the result is a Err value, returnsbackup
.unwrapOrElse(self, f: fn(E) -> O) -> O
- Returns the inner value of the result if it is a Ok value. If the result is a Err value, invokesf
with the inner value and returns the result of the invocation.map<U>(self, f: fn(O) -> U) -> Result<U, E>
- If the result is a Ok value, returns a Ok value with the result of invokingf
on the inner value. If the result is a Err value, returns a Err value with the inner value. WhereU
is the type of the new success value.mapErr<F>(self, f: fn(E) -> F) -> Result<O, F>
- If the result is a Ok value, returns a Ok value with the inner value. If the result is a Err value, returns a Err value with the result of invokingf
on the inner value. WhereF
is the type of the new error value.mapOr<U>(self, backup: U, f: fn(O) -> U) -> U
- If the result is a Ok value, returns the result of invokingf
on the inner value. If the result is a Err value, returnsbackup
. WhereU
is the type of the new success value.mapOrElse<U>(self, backup: fn(E) -> U, f: fn(O) -> U) -> U
- If the result is a Ok value, returns the result of invokingf
on the inner value. If the result is a Err value, returns the result of invokingbackup
on the inner value. WhereU
is the type of the new success value.and<U>(self, other: Result<U, E>) -> Result<U, E>
- If the result is a Ok value, returnsother
. If the result is a Err value, returns a Err value with the inner value. WhereU
is the type of the new success value.andThen<U>(self, f: fn(O) -> Result<U, E>) -> Result<U, E>
- If the result is a Ok value, returns the result of invokingf
on the inner value. If the result is a Err value, returns a Err value with the inner value. WhereU
is the type of the new success value.or<F>(self, other: Result<O, F>) -> Result<O, F>
- If the result is a Ok value, returns the result. If the result is a Err value, returnsother
. WhereF
is the type of the new error value.orElse<F>(self, f: fn(E) -> Result<O, F>) -> Result<O, F>
- If the result is a Ok value, returns the result. If the result is a Err value, returns the result of invokingf
on the inner value. WhereF
is the type of the new error value.
The Option
type is a type that represents a value that may or may not be present.
It is supposed to be used in place of nulls, undefineds and null pointers.
This is one of the ways that SafeScript can guarantee safety.
Signature:
enum Option<T> {
Some(T),
None,
}
Generics:
T
- The type of the value.
Methods:
isSome(&self) -> bool
- Returns true if the option is a Some value.isNone(&self) -> bool
- Returns true if the option is a None value.expect(self, msg: str) -> T
- Returns the inner value of the option. Panics withmsg
if the option is a None value.unwrap(self) -> T
- Returns the inner value of the option. Panics if the option is a None value.unwrapOr(self, backup: T) -> T
- Returns the inner value of the option. If the option is a None value, returnsbackup
.unwrapOrElse(self, f: fn() -> T) -> T
- Returns the inner value of the option. If the option is a None value, invokesf
and returns the result of the invocation.map<U>(self, f: fn(T) -> U) -> Option<U>
- If the option is a Some value, returns a Some value with the result of invokingf
on the inner value. If the option is a None value, returns a None value. WhereU
is the type of the new value.mapOr<U>(self, backup: U, f: fn(T) -> U) -> U
- If the option is a Some value, returns the result of invokingf
on the inner value. If the option is a None value, returnsbackup
. WhereU
is the type of the new value.mapOrElse<U>(self, backup: fn() -> U, f: fn(T) -> U) -> U
- If the option is a Some value, returns the result of invokingf
on the inner value. If the option is a None value, returns the result of invokingbackup
. WhereU
is the type of the new value.okOr<E>(self, err: E) -> Result<T, E>
- If the option is a Some value, returns a Ok value with the inner value. If the option is a None value, returns a Err value witherr
. WhereE
is the type of the error value.okOrElse<E>(self, f: fn() -> E) -> Result<T, E>
- If the option is a Some value, returns a Ok value with the inner value. If the option is a None value, returns a Err value with the result of invokingf
. WhereE
is the type of the error value.and<U>(self, other: Option<U>) -> Option<U>
- If the option is a Some value, returnsother
. If the option is a None value, returns a None value. WhereU
is the type of the new value.andThen<U>(self, f: fn(T) -> Option<U>) -> Option<U>
- If the option is a Some value, returns the result of invokingf
on the inner value. If the option is a None value, returns a None value. WhereU
is the type of the new value.or(self, other: Option<T>) -> Option<T>
- If the option is a Some value, returns the option. If the option is a None value, returnsother
.orElse(self, f: fn() -> Option<T>) -> Option<T>
- If the option is a Some value, returns the option. If the option is a None value, returns the result of invokingf
.xor(self, other: Option<T>) -> Option<T>
- If the option is a Some value, returns a None value. If the option is a None value, returnsother
.filter(self, f: fn(T) -> bool) -> Option<T>
- If the option is a Some value, and invokingf
on the inner value returns true, returns the option. Otherwise, returns a None value.
The Error
is a trait that all error types should implement.
Implementing this trait allows for better error handling and interoperability.
And allows for the use of short-circuiting early returns.
Example:
function read_file(path: str) -> Result<str, FileError> {
// Code here...
}
function main() -> Result<(), dyn Error> {
let contents = read_file("file.txt")?;
// With the Error trait, we can use the ? operator to short-circuit early returns.
// Otherwise, we would have to do this:
// let contents = match read_file("file.txt") {
// Ok(contents) => contents,
// Err(err) => return Err(err),
// };
}
Signature:
trait Error: fmt.Debug + fmt.Display {
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
Methods:
source(&self) -> Option<&(dyn Error + 'static)>
- Returns the underlying cause of this error, if any. (If the error is a wrapper around another error, this method should return the underlying error.) Should default toNone
.
Trait Implementation Requirements:
core.fmt.Debug
- All implementors ofError
must also implementDebug
.core.fmt.Display
- All implementors ofError
must also implementDisplay
.
The panic
function is a function that is used to cause a runtime crash.
You should NEVER use this function.
For exception-like error handling, use the Result
type.
Signature:
fn panic(msg: str) -> ! { }
Parameters:
msg
- The message to display when the panic occurs.
The core.mem
module contains functions for finer control over memory (while still being safe).
It contains things like Vec
Signature:
enum Error {
OutOfMemory,
InvalidPointer,
InvalidSize,
InvalidAlignment,
BitConversionError,
}
Variants:
Indicates that the system is out of memory.
Indicates that the pointer is invalid.
Indicates that the size is invalid.
Indicates that the alignment is invalid.
Indicates that there was an error converting bits.
A type alias for core::Result<T, Error>
.
Where T
is the type of the value.
And Error
is core.mem.Error
.
Signature:
type Result<T> = core.Result<T, Error>;
Generics:
T
- The type of the value.