Differences from Erlang/OTP
Observable behavior where Clause knowingly differs from the pinned OTP 29
(maint-29, see otp-reference.md). Each entry links to the
contract that owns it. Remove an entry once the difference is fixed. Features
not implemented yet (reported as notimpl) are not listed here; see
features.md and the plan.
Maps
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Map generator order (K := V <- M) | Flat maps (up to 32 keys) iterate in key order, but atom keys in atom-table order, which varies between VM runs; larger maps in hash order | Always canonical key order (term order) | patterns |
bad_generators payload of a map generator in a zip group | Its iterator: {K, V, Next} chain ending in none, in OTP's order | The same chain, built in canonical key order | patterns |
maps:iterator/1,2, maps:next/1, erts_internal:map_next/3, the order maps funs run in (intersect_with/3, merge_with/3, ...), proplists:get_keys/1, sets of version 2 (to_list/1, fold/3) | Internal order (atom-table order for atom keys, hash order above 32 keys); an iterator is a position or a {K, V, Next} chain | Canonical key order; an iterator continues over a key list | builtins |
Map printing (erlang:display/1, ~w, ~p) | Internal layout order (atom-table order for atom keys, hash order above 32 keys) | Map-key order, as OTP's ~kw | terms |
Order of digraph:vertices/1, edges/1, source_vertices/1, sink_vertices/1 and of digraph_utils' orderings and components where they start from those lists | The vertex and edge tables' hash order | Clause's hash order (ETS); paths and neighbour lists follow edge insertion in both | builtins |
Order of maps:keys/1, values/1, to_list/1, fold/3, of the members json:encode/1 writes, of the fields erl_parse:abstract/1,2 and erl_syntax:abstract/1 give a map, and of graph's vertex lists and traversals | The map's internal order (atom-table order for atom keys, hash order above 32 keys) | Key order | library |
Native record printing (erlang:display/1) | Fields in atom-table index order | Definition order, as ~w | native records |
== between native records whose fields differ only as integer/float | The compiler may fold it to =:= (false) | Numeric comparison (true) | native records |
| External native record construction of a module that is not loaded | Fails until the defining module is loaded | Every batch module is loaded at startup; a module outside the batch always fails | native records |
Local fun printing (#Fun<M.Index.Uniq>) | Index from the compiler's lambda numbering, Uniq a hash of the module code | Index of the module's local funs in source order, Uniq always 0 | funs |
| Order of two local funs of one module | By OTP's index | By source-order index, so funs of different functions can order differently | funs |
Anonymous fun names in stack traces (-f/1-fun-N-) | N from the compiler's numbering | N counts the funs of f/1 in source order | funs |
| An anonymous fun in a record field default | Each construction site expands its own copy: funs from two sites are unequal | One fun for the default: funs from any construction site are equal | funs |
| Captured value order of funs created inside comprehensions | Free variables of the comprehension's generated function | Definition order in the enclosing function; visible only when comparing two such funs | funs |
Pids, references and processes
| Difference | OTP | Clause | Owner |
|---|---|---|---|
timer one-shot and interval timers other than sends to local pids | Run by the timer_server process, started under kernel_sup on first use; exit_after/2,3 signals come from it | No timer server: each timer is a process of its own, which sends exit_after's signal; cancel/1 returns before that process has seen the cancellation | builtins |
| Pid numbers | The first user process is about <0.80.0>; numbers are reused after the pid table wraps | The first process is <0.1.0>; numbers come from one sequence and are never reused | terms |
Code after spawn(Fun) of a fun of another arity | The compiler's type analysis may treat the code after the call as unreachable and drop it, so the caller returns the pid at once | The caller goes on; only the new process fails with {badarity, {Fun, []}} | processes |
Stack of a function_clause raised by a lists builtin (lists:sublist/2, lists:max/1, ...) | The top frame is the Erlang function, often an internal helper (lists:sublist_2/2), with its arguments | The caller is on top: builtins push no frame | builtins |
Last bit of math results (math:erfc(1), ...) | The C library OTP was built with | The C library Clause programs link (Windows: 0.15729920705028513 for erfc(1), OTP 0.1572992070502851) | builtins |
binary:compile_pattern/1 result | {bm, Ref} or {ac, Ref} with an opaque reference | {bm, [Pattern]} or {ac, Patterns} | builtins |
binary:referenced_byte_size/1 of a sub-binary | The size of the binary it refers into | Its own size | builtins |
| Memory of replaced or erased persistent terms | Reclaimed by a literal-area collector process that has every process copy the value out, in the background | Each process copies it out at its next slice (parked ones at once, by the put/erase), then the area is freed; no collector process | runtime |
| Persistent terms sent in messages | Stay shared, like literals: the receiver's heap does not grow | Copied into the receiver's heap like any other term | runtime |
persistent_term:info/0 and get/0 | Include the terms OTP's kernel and stdlib store at boot (26 on OTP 29) | Only the program's own terms | builtins |
code:is_loaded/1, code:which/1, code:all_loaded/0 of a compiled module | The .beam file it was loaded from | preloaded: every module is linked into the executable | builtins |
priority option of send/3, link/2, exit/3, exit_signal/3 | Priority messages and signals overtake ordinary ones | Accepted; delivered as ordinary messages and signals | processes |
spawn/2,4, spawn_link/2,4, spawn_monitor/2,4 naming another node | A warning report ** Can not start ... on Node ** is logged | Nothing is printed; the useless pid still ends with noconnection | processes |
system_flag/2 flags other than backtrace_depth and min_heap_size (fullsweep_after, schedulers_online, multi_scheduling, max_heap_size, ...) | Change the emulator | Checked and read back without an effect; system_logger starts as undefined (no logger process); cpu_topology, erts_alloc, scheduler_bind_type are badarg | builtins |
A suspended process's timers (receive ... after, send_after/3 and start_timer/3 to its pid) | Suspended with the process until it is resumed (OTP 28, 29) | Keep running: a timeout that expires while suspended is taken once the process resumes; a timer message waits in its mailbox | processes |
process_display/2 output | OTP's stack dump with addresses and stack slots | One Module:Function/Arity per frame | processes |
process_flag(save_calls, N) | Saves the last N calls for process_info(Pid, last_calls) | Kept and read back; no calls are saved | processes |
adler32_combine/3, crc32_combine/3 with a second size above 2^31 - 1 on Windows | Combined | badarg (zlib's z_off_t is 32 bits there) | builtins |
init:get_arguments/0, init:get_argument/1 | The emulator's flags: root, bindir, progname, home and every -Flag of the command line | Only home; an executable has no emulator flags | builtins |
init:script_name/0, init:script_id/0 | The boot script's path and {"Erlang/OTP", "29"} | The executable's absolute path and {"Clause", "29"} | builtins |
init:get_status/0, init:ensure_loaded/1 in the entry | {starting, started} and code loading while escript, -s or -eval code runs during boot; {started, started} and not_allowed once started | Always the started answers | builtins |
init:stop/0,1, init:reboot/0 | Return ok; init then takes the applications down and halts, while the caller keeps running | Halt at once; the call never returns | builtins |
init:get_argument(home) on Windows without HOMEDRIVE/HOMEPATH | The shell's profile folder | USERPROFILE, normally the same folder | builtins |
erlang:halt(Slogan), init:stop(Slogan) | Prints the slogan, writes erl_crash.dump in the current directory and says so on stderr; exit status 1 | Prints the slogan on stderr, writes no crash dump; exit status 1 | executables |
init:restart/0,1, init:boot/1, init:make_permanent/2 | Restart the system in the same node, boot it, or set the boot script of the next restart | Refused: notsup error, {error, notsup} from make_permanent/2 | builtins |
atomics:new/2 arity | Any positive integer the allocator can serve; a huge one ends the node with a crash dump | 1 to 2^27, else badarg | builtins |
counters with write_concurrency | Per-scheduler counters summed on read; info/1 memory grows with the scheduler count | One atomics array; info/1 memory is the array's | builtins |
erts_internal:term_type/1 | The internal representation: flatmap or hashmap, heap_binary, refc_binary or sub_binary, ... | Every map flatmap, every bitstring sub_binary | builtins |
erl_prim_loader:get_path/0 | The boot code path | ["."] until set_path/1 | ports |
file:read_link_info/1,2 of a symbolic link on Windows | The link's own reparse-point data | The target's size and times with type symlink | ports |
inet:gethostbyname/1,2 | The resolver's canonical name and aliases (inet_res, hosts file, native) | The name asked and no aliases; addresses from the system resolver | ports |
socket module | The socket NIF API | Missing: a reference is a compile-time error; use gen_tcp, gen_udp, inet | library |
net:getifaddrs/1,2 with a namespace, inet:getifaddrs([{netns, _}]) | net calls itself without end (OTP 29); inet reads the namespace on Linux | {error, enotsup} | builtins |
zlib output lists | One binary per internal chunk, nested where a stream ended and restarted | One binary per call, [] when there is none; the bytes are OTP's | builtins |
zlib:safeInflate/2 and long zlib calls | Return after a bounded amount of output ({continue, Output}) and yield | Inflate all queued input in one call ({finished, Output}); a long call holds its scheduler thread | builtins |
Long re matches | re:run/3 and the global loop yield after a bounded amount of work | One match runs to its end and holds its scheduler thread; a global run yields between matches only | builtins |
re:compile/2 with export, re:import/1 | An exported pattern {re_exported_pattern, ...} that another node imports | export is badarg; import/1 raises {not_implemented, import, 1} | builtins |
re argument errors | badarg raised from re:run/3 (or replace, split) with the caller's arguments and error_info | badarg raised from the re helper that rejected the argument, with that helper's arguments | builtins |
code:is_sticky/1 | true for modules of kernel, stdlib and compiler, and for the modules of directories code:stick_dir/1 named | Always false; stick_dir/1 only checks that the directory can be listed | builtins |
code:root_dir/0 and the initial code path | OTP's installation directory; the path holds its lib/*/ebin directories | The executable's directory; the path holds the ebin directories of lib/ beside it and of ERL_LIBS, then "." | builtins |
code:load_file/1, load_abs/1, load_binary/3, atomic_load/1, prepare_loading/1 with valid object code | Load the module | {error, badfile}: every module is linked into the executable (D01) | builtins |
code:coverage_support/0 and the coverage functions | true with the JIT; coverage modes and counters | false; the others raise badarg, as OTP does without coverage support | builtins |
code:get_doc/1 of a module the program has | Its EEP 48 documentation chunk | {error, missing}: executables embed no documentation | builtins |
Formatting a large term (io:format/1,2) | Runs in Erlang code (io_lib) and the group leader, so the process can be preempted while formatting | Runs to completion in one builtin call; other processes wait | builtins |
process_flag/2 flags | trap_exit, priority, message_queue_data, min_heap_size and others | Only trap_exit and min_heap_size; every other flag raises badarg | processes |
| Error report order of processes crashing at the same time | Logger order | The order their workers finish them | processes |
Exit signals (exit/2, exit_signal/2) to a process running on another scheduler | Queued in its signal queue and handled later, while the sender goes on | The sender waits until the target's time slice ends, then acts at once; observable only as timing (messages are buffered and the sender goes on) | processes |
term_to_binary/1 of a local fun or a native record | Encoded (NEW_FUN_EXT, ...) | error:notsup; fun M:F/A is encoded | builtins |
term_to_binary/1 of a map with more than 32 keys | Hash order unless deterministic | Always key order (deterministic's bytes) | builtins |
binary_to_term/1 of another node's pid, port or reference; option local of term_to_binary/2 | External identities; LOCAL_EXT encoding | badarg | builtins |
| Timer time limit | Depends on the native time unit (about 2^49.7 ms on Windows) | Relative time 0..2^49-1 ms on every platform | processes |
unique_integer/0,1 without positive | May be negative | Always positive and increasing | builtins |
process_info/2 items current_function, initial_call, current_stacktrace, ... | Reported | error:{notsup, Item}; process_info/1 omits them | processes |
process_info/2 reductions; statistics/1 reductions, garbage_collection, io | Counted | 0 (not kept) | builtins |
garbage_collect/0,1,2 | Collects before returning | Collects at the target's next safepoint | processes |
erlang:system_info(machine), and so epp's ?MACHINE and the macro it names | "BEAM", 'BEAM' | "Clause", 'Clause' | builtins |
erlang:system_info(schedulers) of a program started with an affinity mask (start /affinity, taskset) | One scheduler per logical processor of the host (32 under a 7-CPU mask), schedulers_online one per CPU of the mask | Both one per CPU of the mask: Clause starts only the online workers | processes |
spawn_opt/2..5 tuning options (priority, min_heap_size, ...) | Applied | Checked and accepted, not applied | processes |
hibernate/3 | Discards the stack; the process ends when M:F returns | Keeps the stack; M:F's result returns to the caller of hibernate/3 | processes |
monitor(port, Item, Options) with options | Accepted | badarg | processes |
spawn_request_abandon/1 | true while the reply is pending | Always false: the reply is sent before spawn_request returns | processes |
math:log2/1 last bit (Windows) | OTP's Windows build divides log(X) by log(2) (math:log2(1.0e308) is 1023.1538532253077) | The C library's log2 (1023.1538532253076) | builtins |
phash/2, phash2/1,2 of pids, ports, references, funs and native records | Hashes of OTP's process, port and reference numbers, a fun's index and unique number, a record definition's hash | Hashes of Clause's numbers, a fun's module, arity or index and captures, a record's field values; every other term hashes to OTP's value | builtins |
dict:fold/3, map/2, filter/2 with a non-fun | function_clause, or {badfun, F} when the first bucket OTP visits holds an entry | function_clause | builtins |
erlang:is_builtin/3 of lists:reverse/2 and other C-implemented library functions | true | false: only the runtime's builtins and apply/2,3 | funs |
fun_info/1,2 pid, uniq, new_uniq of a local fun | Creator pid, compiler checksums | undefined, MD5 of the module name | funs |
md5_init/0 context | OTP's 92-byte state | Clause's 96-byte state; digests are equal | builtins |
erlang:delete_module/1 of a loaded module | Makes its code old (true) | error:notsup: linked code stays | builtins |
erlang:prepare_loading/2 | Prepares a valid BEAM binary | Always {error, badfile} | builtins |
erlang:pre_loaded/0 | The preloaded boot modules | Every module of the program | builtins |
list_to_pid/1 of a number never issued | Returns a pid term | badarg: only pids this program created are admitted | terms |
list_to_port/1 of a number never issued | Returns a port term | badarg: only ports this program opened are admitted | ports |
port_info/1,2 values | id is a table index; memory, queue_size, locking describe the driver | id is the port number; memory and queue_size are 0; locking is port_level | ports |
| Writing a file opened only for reading | {error, eacces} on Windows, {error, ebadf} elsewhere | {error, ebadf} everywhere | ports |
win32reg's port | The linked-in registry__drv__ port ({spawn, "registry__drv__ rw"}), answering commands with port messages | The clause_registry driver ({spawn_driver, ...}), answering the same replies through port_control/3; port_info/2 names it clause_registry | ports |
argparse's program name without the progname option | The emulator's progname (erl) | The executable's name without its extension (init has no progname) | library |
Top stack frame of an argparse command validation error | The validating function (argparse:validate_command/2, validate_option/2, a fun inside them) with [Entity] | argparse:validate/2 with [Entity] | builtins |
escript:script_name/0 | The escript's path, the first plain argument | The executable's path: a Clause program is its own script | library |
escript:extract/2 with compile_source | The source compiled to a beam | {error, {compile, ...}}: Clause programs have no compiler | library |
erl_abstract_code:debug_info(core_v1, ...) (beam_lib's Core Erlang) | Core Erlang from the compiler | {error, failed_conversion}: Clause programs have no compiler | library |
zstd:get_frame_header/1 of a truncated frame header | The fields of uninitialized memory | The fields zero ('ZSTD_frame', sizes 0) | builtins |
file:open/2 with raw or ram | A #file_descriptor{} record served in the caller (is_pid/1 false); io requests on it fail; a ram file has no read_line/1 (undef) and a closed one answers {error, einval} | A file server pid like other files; positioned I/O and sendfile/5 answer as for OTP's raw files; a ram file answers read_line/1 and I/O requests with {error, enotsup}, and {error, terminated} once closed | ports |
file:truncate/1 on a file opened only for reading | The platform's error (eacces on Windows, einval elsewhere) | The C runtime's error for the descriptor (ebadf or einval); the server stops as OTP's | ports |
Files opened or set to {utf16, _} or {utf32, _} | Lines and characters decoded in the encoding | Lines end at a byte 10 before they are decoded | ports |
io_ansi sequences on Linux and macOS | From the terminfo database of TERM where it has the capability (setaf, sgr0, ...); tput/1,2, tigetnum/1, tigetflag/1, tinfo/0 read it | The built-in ANSI sequences OTP uses without terminfo (as OTP on Windows); tigetnum/1, tigetflag/1, tinfo/0 answer {error, enotsup}, tput/1,2 raise {enotsup, Capability}; enabled/0,1 wants a TERM other than dumb | library |
io_ansi:scan/1 and tput/1,2 on Windows | Fail with try_clause on terminfo's {error, enotsup} | scan/1 matches the built-in sequences; tput/1,2 raise {enotsup, Capability} | library |
socket on Windows: recvmsg, sendmsg, sendfile, sendmmsg, recvmmsg | Raise notsup | Work as on Linux and macOS | library |
socket on Windows: a datagram longer than recv's length | {error, {completion_status, ...more_data...}} | The datagram truncated, as elsewhere | library |
socket on Linux and macOS: ioctl/2,3,4, getopt_native/3, setopt_native/3, open/1,2 of a descriptor, local domain, SCTP | Supported | Refused as OTP on Windows refuses them ({error, {invalid, ...}}, notsup); local is {error, eafnosupport} | library |
socket:info/1, i/0,1,2 | The NIF's counters and states, fd the descriptor | Counters of the calls made, states as the module tracks them; Fd is the port number | library |
inet_res answers | gethostbyname, getbyname and gethostbyaddr answers are cached by TTL in inet_db; a cached lookup can list addresses in another order | Nothing is cached: every lookup asks the name servers | library |
| Resolver configuration on Linux and macOS | inet_db also reads host.conf/nsswitch.conf and inetrc files, follows changes to resolv.conf, and on Linux derives the domain from the host name | resolv.conf's nameserver, domain and search lines and the hosts file, read once | library |
os:set_signal(Signal, handle) (Linux, macOS) | The signal is sent to erl_signal_server, whose default handler stops the system on sigterm, halts on sigusr1 and sigquit | No erl_signal_server: sigterm and sigquit exit with status 0, sigusr1 with status 1, others are dropped; without set_signal, SIGTERM keeps the system's default action | ports |
Empty arguments of {spawn_executable, F} on Windows | Dropped from the command line | Passed as "" | ports |
{exit_status, S} and eof of a spawned program | Unspecified order, exit_status may arrive before the last data | After all data, before eof | ports |
| Busy ports | A port with too much queued output suspends senders, socket ports included; port_command/3 force works on drivers that allow it | Only ports of spawned programs become busy; fd and socket ports never do; force raises notsup on every driver | ports |
{active, N} of sockets | An integer N counts messages before {tcp_passive, S} | exit(badarg); only true, false and once | ports |
Socket options of inet_drv itself (header, deliver, exit_on_close, send_timeout, send_timeout_close, delay_send, watermarks, packet_size, show_econnreset, buffer, read_packets, recvtos, recvttl, line_delimiter) | Applied | Kept and reported by getopts/2 with OTP's defaults, not applied; the system's options (nodelay, keepalive, linger, buffers, tos, multicast, ...) are applied | ports |
Other socket options ({packet, line | http | ...}, {raw, ...}, bind_to_device, netns) | Work | exit(badarg) when opening, {error, einval} from setopts/2 and getopts/2 | ports |
inet:getopts(S, [multicast_if]) on Windows | Reads uninitialized memory for a TCP socket, and the any address after a UDP socket set one | The system's value; the any address when the socket has none | ports |
port_info(Socket, input | output), inet:info/1's input and output | The bytes of the driver commands and replies | 0: socket traffic is counted by inet:getstat/1,2 only | ports |
| Socket error reasons | Every POSIX reason the system reports | The common ones (econnrefused, eaddrinuse, econnreset, etimedout, ...); others are eio | ports |
| Port input while the connected process holds 1,024 messages and can run | Keeps reading and delivering | Stops delivering until the process's time slice ends; past 64 KiB held, stops reading, so the writing program blocks | ports |
gen_tcp:close/1 with queued output | Waits up to the linger/send_timeout setting for the output to leave | Returns at once; the I/O thread sends the queued output, then closes | ports |
monitor/2 types | process, port, time_offset | process and port; time_offset raises badarg (no time offset changes) | processes |
exit/2, exit_signal/2 to a reference | Sends to the process alias, if the reference is an active one | Nothing: exit signals never go to aliases | processes |
| Reference numbers | Mix a scheduler identifier and per-scheduler counters (#Ref<0.178111994.4235460610.214105>) | One program-wide counter (#Ref<0.0.0.1>), so references order by creation | terms |
Errors, stack traces and reports
| Difference | OTP | Clause | Owner |
|---|---|---|---|
| Stack trace locations | [{file, F}, {line, L}], error_info for error/3 | Always []; no error_info, so erl_error:format_exception/3,4 shows no locations or *** argument lines for them | ABI |
erlang:raise/3 with {Fun, Args} stack entries | Accepted | Rejected | ABI |
function_clause top frame | Argument list | Arity | ABI |
undef top frame of a M:F(A1, ..., An) call | {M, F, [A1, ..., An], []} | {M, F, N, []}: the arity in place of the arguments (apply/3 and fun calls show the arguments) | funs |
| Dynamic call of an export an executable cannot reach (its module named only through a stack trace, an exception or a message from outside the program) | Calls it | undef, unless built with --keep-exports; function_exported/3 and module_info(exports) still list it | executables |
| Failing BIF/operator frames | Present ({erlang, '+', Args, ...}) | Absent; nothing below the entry function | ABI |
Atom table full (list_to_atom/1) | The emulator aborts (no more index entries in atom_tab) and writes a crash dump | clau: runtime failure: entry call failed: resource_limit, exit 70 | builtins |
list_to_integer/1,2 with characters above 255 | Its first digits use only each character's low byte ([16#131] is 1) | badarg | builtins |
erlang:function_exported/3 of a BIF | True for every BIF of the emulator | True only for the builtins this runtime provides | builtins |
| Error reports of crashed processes | Sent to the logger, written later by its default handler (on standard output under erl, often lost when an escript halts first) | Written on stderr when the process ends, before any later output; not a logger event, so handlers and filters do not see it | processes |
| Calls to functions that never return | Compiled as tail calls (caller missing from the trace) | Ordinary calls (caller present) | ABI |
Exceptions inside a known anonymous fun at -O2 (F = fun ... end, F(X), lists:map(fun ... end, L) and the other list loops, maps:fold(fun ... end, A, M)) | Frames of the fun (-f/1-fun-0-) and of the lists and maps helpers (lists:map_1/2) | The fun's body runs inlined in the caller: those frames are missing, and an improper list tail of map/2, foldl/3, all/2, any/2, flatmap/2, filtermap/2, partition/2, search/2, takewhile/2, dropwhile/2, splitwith/2, zipwith/3, zipwith3/4 (and lists of different lengths) raises function_clause with the caller on top | funs |
Exceptions inside a small local function (leaf) at -O2, called locally or as ?MODULE:f() (badarith, badmatch, a failing builtin) | The function's frame is in the stack trace | The leaf's body, and those of the leaves it calls, run inlined in the caller: their frames are missing and the caller is on top; function_clause (no clause matching) still has the function on top | specialization |
Profiles and the Erlang stack of inlined code at -O2 (known funs, small local functions) | Every call counts for the called function (eprof, tprof), and its frame is on the stack | Inlined code runs in its caller's frame: its entries and time are the caller's, and debuggers see it only as an inlined native frame | profiling |
| Host memory exhausted | The emulator reports that it cannot allocate memory, writes a crash dump and stops | clau: runtime failure: entry call failed: out_of_memory, exit 70, no dump | runtime heap |
| Storage of maps above 32 keys | An on-heap HAMT of 16-way hashed nodes | An on-heap B+ tree of up to 16 entries per node in key order; heap sizes of large maps differ from OTP's | runtime heap |
| Process memory cap | max_heap_size (in words, heap and stack) kills the process with reason killed and logs an error report | --max-heap / --max-stack (bytes) and the runtime-wide --max-memory fail the requesting process as resource_limit: clau: runtime failure: entry call failed: resource_limit, exit 70 | runtime heap |
| Program arguments | escript passes every argument to main/1; emulator flags (+t) come from %%! or ERL_FLAGS | Leading runtime options (--max-atoms, --max-heap, --max-stack, --max-memory, --min-heap-size, --args-file, --) are taken out first; CLAUSE_FLAGS holds the same options | executables |
| Uncaught exception in an ordinary entry module | escript exits 127 | Exits 1 with one uncaught exception <class>: <reason> line (escript sources keep 127) | executables |
| Compiler diagnostics | erl_lint wording (variable 'X' is unbound) and warnings | Own wording (unbound variable X); OTP lint warnings are mostly not emitted (also native record warnings: unknown fields in access, update and patterns, own-module qualified construction without values, unused or header-defined records; a later pattern matching a name exported from a branch) | semantic |
Behaviour module with a hand-written behaviour_info/1 | Calls it while compiling users and warns about missing, ill-defined or deprecated callbacks | Not evaluated: its users' callbacks are not checked | semantic |
Behaviour modules neither in the batch nor in the Clause library (application, ...) | Loaded from the code path; their callbacks are checked | behaviour M undefined warning | semantic |
Default logger handler | logger_std_h writing asynchronously with overload protection (sync, drop and flush modes, burst limits, overload kill) | Also on standard_io (standard_error with CLAUSE_LOGGER_TYPE=standard_error), but each event is written before the logging call returns; the overload options are checked and kept without an effect | library |
Handler processes of logger_std_h | Supervised by logger_sup, restarted after overload_kill_restart_after | Linked to the logger server; a handler whose process dies is removed | library |
net_adm:names/0,1 | Asks epmd: {ok, Names} when an epmd runs on the host | {error, address}: Clause has no epmd client | library |
net_kernel:start/1,2 | Starts distribution, or OTP's error when it cannot | Not available (not_implemented) | library |
Tracing: erlang:trace/3, trace_pattern/2,3, trace_info/2, trace_delivered/1, system_monitor/0,1,2, system_profile/0,2, seq_trace/2, seq_trace_info/1, seq_trace_print/1,2, dt_*, the trace module, erl_tracer:enabled/3, trace/5 | Trace | error:notsup | builtins |
seq_trace tokens | Passed on with messages; serial numbers count them; a system tracer receives trace messages | Kept per process only: not passed on, serials stay as set, no trace messages | library |
pg scopes and global | Replicate between connected nodes; pg monitors nodes through net_kernel | One node, no peers or node monitoring | library |
| Application resources | .app files found on the code path when loaded; sys.config, -config and -App Key Value arguments set environments | Compiled into the executable from the build's resource files; changing them needs a rebuild; no configuration files or arguments | library |
kernel and stdlib applications | Started by boot; kernel has a master and a top supervisor | Loaded and running without masters: get_supervisor(kernel) is undefined, info/0 lists them as running undefined | library |
application:ensure_all_started/3 with concurrent | Starts independent applications at once; the list follows completion order | Starts them one after another, dependencies first | library |
application:takeover/2 | Waits for the distributed application controller | Exits noproc (no dist_ac), as load/2 with nodes does on both | library |
| I/O requests to an application master | Relayed to the group leader the application started under | Answered on standard I/O | library |
erlang:process_flag(error_handler, Module) | The runtime calls Module:undefined_function/3 and undefined_lambda/3 for undefined calls | badarg; process_info/2 always reports error_handler, and undefined calls raise undef directly, except calls into interpreted modules, which enter error_handler:undefined_function/3 | library |
Crash report details (proc_lib) | erl_error's exception text with extended error information; neighbours with current_function, initial_call and current_stacktrace; real reductions | The exception's class line and stack frames without extended error information; neighbours without those items; reductions 0 | library |
client_info of gen_server, gen_statem and gen_fsm error reports | The calling process's current_stacktrace | []: Clause keeps no current stack of another process | library |
-behaviour with a value that is not an atom | Warning prints the term with ~w (behaviour [115,116,114] undefined) | Prints the attribute's source text (behaviour "str" undefined); the error is the same | semantic |
module_info(md5) and the vsn OTP derives from it | MD5 of the compiled BEAM code | MD5 of the module's printed source; the derived vsn integer differs accordingly | semantic |
module_info(compile) | version of OTP's compiler application, options given to it | Clause's version; options always [] | semantic |
module_info(functions) | Also lists the functions OTP generates for funs ('-f/0-fun-0-'/0) | Source functions and the predefined ones only | semantic |
-nominal in module_info(attributes) | Kept as {nominal,[{Name,AbstractType,Params}]} with abstract format | Left out | semantic |
A function of a Clause library module that Clause has not implemented (an erlang BIF, lists:rmerge/2, ...) | Runs | Compiles; raises error({not_implemented, F, A}) when called, with M:F/A on top of the stack (statuses) | library, builtins |
| Import of a module outside the batch | Compiles; the call raises undef at run time if the module is missing | unknown module M compile error when the import is called (unused imports need no module) | semantic |
import directive overrides auto-imported BIF warning | For every auto-imported BIF of erl_internal:bif/2 | Only for the auto-imported BIFs Clause implements | semantic |
Attributes after the first function definition other than records, types, -spec, -doc, -moduledoc and -dialyzer (-export, -import, -compile, -behaviour, user attributes) | Error attribute A after function definitions | Accepted (only -export_record is rejected) | semantic |
Language edge cases
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Integer segment wider than the integer limit, value past it (<<V:4194241>> of all ones) | The x86 JIT matches with an invalid term; using it crashes the VM | No match | terms |
| Zip group whose relaxed and strict generators share a variable | Skip test keeps strict-pattern variables in the relaxed patterns | A rejected step is skipped whenever the strict patterns match on their own | patterns |
Calling an external fun, M:F(Args) or apply/3 of a module outside the program | Loads the module from the code path, undef when absent | undef; library modules join the program only when it names them with a literal atom | funs, library |
Files without #! given as escripts | escript file.erl skips the first line | Compiled as ordinary modules | executables |
| Precompiled beam and archive escripts | Run | Not supported | executables |
Module name of an escript without -module | <name>__escript plus a timestamp/unique suffix | <name>__escript, deterministic | executables |
include_lib of an application found in several <app>-<version> directories | The code path holds one version (boot picks the highest); no diagnostic | The highest version below each searched directory, with one warning per run listing them | preprocessor |
A -spec whose types share no value with the inferred result, entry domain or a call's arguments | Compiles; Dialyzer may warn | A contradicted argument entry domain is a compile error naming the declared and inferred types; a contradicted result or call is a warning, and the inferred types are used | semantic analysis |
ETS
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Order of first/1, next/2, tab2list/1, foldl/3 and i/1 over set, bag and duplicate_bag tables, and what slot/2 numbers | Linear hash slots visited lock by lock; the order changes as the table grows | Parts, then buckets in order, then keys in insertion order within a bucket; a slot is one bucket | runtime |
ets:all/0 | Includes the tables of OTP's kernel and stdlib; order of the scheduler replies | Only the program's tables, in no particular order | builtins |
info/2 memory | Allocated words of the table and its objects | Stored object words plus Clause's own overheads | builtins |
{write_concurrency, auto} | Lock count adapts to contention (64 to 8192) | 64 fixed parts, as for true; stats reports 64 locks | runtime |
compressed | Stores elements other than the key in external format | Accepted and reported; objects are stored as they are | builtins |
info/2 stats of an ordered_set with write_concurrency; data; binary | CA tree {RouteNodes, BaseNodes, MaxDepth}; prints the table; the table's off-heap binaries | false; true without printing; [] | builtins |
badarg of an ets builtin | Carries error_info with a cause (id, access, type, ...) | Plain badarg | builtins |
Order of select, match and match_object results of hash tables, and which results each chunk of select/3 holds | Results of each walk step consed, newest first; linear hash slots | Walk order of Clause's parts and buckets; each chunk the first Limit results gathered | builtins |
select_delete/2, select_replace/2 over a whole table | Trap every 1000 objects (the table fixed meanwhile) | Run in one piece, each part under its lock | runtime |
| Match specifications naming a variable numbered 25 or above | Renumbers every '$N' atom of the specification, even inside {const, ...} (so {const, '$30'} gives '$0') | Variables keep their numbers; {const, '$30'} gives '$30' | runtime |
| Match specification guards and bodies nested deeper than 1000 levels | Compile until the C stack runs low (system_limit) | system_limit | runtime |
Trace actions in erlang:match_spec_test/3: enable_trace, disable_trace, trace with valid flags | Change the caller's trace flags (29.1.1 crashes without a tracer) | true after checking the flags (and that a pid argument is alive); nothing changes | builtins |
ets:tab2file/2,3 and file2tab/1,2 | Through a disk_log process named by a fresh reference | Written and read directly with file | builtins |
{process_dump} and {current_stacktrace} in erlang:match_spec_test/3 | The process's dump text; its stack trace | <<>>; [] | builtins |
dets
| Difference | OTP | Clause | Owner |
|---|---|---|---|
| Requests waiting together at a table | Lookups, inserts and deletes queued at once share one cache update; lookups are answered before the writes are made; a fixed table makes some writers sleep 1 ms | Each request in turn, in arrival order | builtins |
Long dets operations (repairs, init_table/2,3, compactions, flushes of a large cache) | Erlang code, preempted by reductions | One builtin call each; the table's worker runs nothing else meanwhile | builtins |
dets processes | The dets server and dets_sup under kernel_safe_sup, tables under dets_sup | A dets server started on first use, unsupervised; table processes linked to it | builtins |
dets:all/0 order | The registry's ETS order | Clause's ETS order (ETS) | builtins |
| Reports | A repair is logged at info level; verbose/0,1 and DETS_DEBUG log more; an internal failure is {dets_bug, ...} | Nothing is logged | builtins |
io
| Difference | OTP | Clause | Owner |
|---|---|---|---|
~p, ~w and io_lib writers of containers nested more than 256 deep | Printed | system_limit | io |
io_lib:print/4 with a column or line length that is not an integer | Depends on the term: printed (guards fail quietly), function_clause or badarith | badarith for any container term | io_lib |
Negative precision or pad count (~.*c with -1) | Loops forever | badarg | io |
erl_scan
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Location of a tokens/3,4 continuation (continuation_location/1, undocumented) | Where the scanner stopped, inside the unfinished token | The start of the unfinished token, which is scanned again with the next characters | erl_scan |
{compiler_internal, [ssa_checks]} | Scans %ssa% check comments into tokens | Ignored: they stay comments | erl_scan |
A based float (16#1.8#e1, base other than 10) whose digits exceed 53 bits | The mantissa converted as ERTS converts big integers | Accumulated in a double; the last bit may differ | erl_scan |
| A list element that is not a character inside a string, quoted atom or escape | function_clause, case_clause or if_clause, depending on the clause | function_clause | erl_scan |
erl_parse
| Difference | OTP | Clause | Owner |
|---|---|---|---|
'%ssa%' tokens (SSA check clauses of OTP's compiler tests) | Parsed into ssa_check_when clauses | A syntax error; erl_scan never produces the token | erl_parse |
erl_eval
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Stack trace of an error the evaluator raises ({badmatch, V}, undef of a local call, ...) | The evaluating process's current stack (process_info/2 current_stacktrace) | The stack at the evaluator's throw that collects it, in Clause's frames | erl_eval |
Module interpreter
Compared with the same module compiled by compile:forms/2 and loaded by code:load_binary/3.
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Stack trace an interpreted try or catch sees for an exception raised in its own body or by compiled code it calls | The module's frames | erl_eval's and the interpreter's frames; an exception that left an interpreted function call has the interpreted frames | library |
Frames of interpreted funs (-f/1-fun-N-) and of compiled code between interpreted calls (lists:map/2 calling an interpreted fun, compiled code calling back into an interpreted module) | Present | Absent: the interpreted functions' frames, then the compiled frames below the outermost interpreted call; a fun's lines count for the interpreted function it runs under | library |
fun F/A of an interpreted function, and interpreted funs | Funs of the module | erl_eval funs; at most 20 arguments ({argument_limit, _}) | library |
module_info(compile), module_info(md5) | The compiler's version, options and source; the object code's MD5 | [{options, []}, {source, File}] after clause_interp:load/1, the image's compile information after code:load_binary/3; the MD5 of the forms | library |
erlang:get_module_info/1,2 and erlang:hibernate/3 of an interpreted module from compiled code | Work | badarg, undef (M:module_info/0,1 works) | builtins |
-on_load of an interpreted module | Runs when the module loads | Not run | library |
| Process dictionary of a process running interpreted code | Only the program's keys | Also '$clause_interp_stack' (the interpreted frames) and '$clause_interp_raised' | library |
compile
compile:file/1,2 and compile:forms/1,2 compared with OTP's compile.
| Difference | OTP | Clause | Owner |
|---|---|---|---|
The binary of forms/1,2 and of file/1,2 with binary, and the .beam file written | BEAM code | An interpreter image: an AtU8 chunk with only the module, Attr, CInf, Dbgi, extra chunks and the forms (ClFm); no Code, ImpT, ExpT, LocT, Line, Type or Docs chunk. Only Clause programs with clause_interp load it | library |
The vsn attribute of a compiled module and beam_lib:md5/1 | MD5 of the BEAM code | MD5 of the forms (as module_info(md5)) | library |
Warnings of the passes after erl_lint (v3_core, sys_core_fold, ...: no clause will ever match, the result of an expression is ignored, ...) | Reported | Not reported: only erl_lint's, epp's and parse transforms' | library |
Options of the Core Erlang, SSA and assembly passes (to_core, dssa, 'S', to_asm, core_transform, ...), from_core, from_asm, makedep, dcover, encrypted debug_info | Listings, Core or assembly input, dependencies, encrypted debug information | Error {none, compile, {clause_unsupported, Option}}: "the Option option needs OTP's code generator, which Clause does not have" | library |
-moduledoc/-doc attributes | Saved in the Docs chunk unless no_docs | Removed; no Docs chunk | library |
code:load_binary/3 of a binary of another module | {error, badfile} and an error report on the logger | {error, badfile}, no report | library |
code:load_binary/3 in a program without compile or clause_interp | Loads BEAM code | {error, badfile}, as for any binary (D01) | library |
erl_syntax
| Difference | OTP | Clause | Owner |
|---|---|---|---|
An error raised while revert_forms/1 reverts a form (a malformed node) | Caught and raised again as exit({Reason, Stacktrace}) | Raised as the error it is | syntax_tools |
concrete/1 of a binary tree whose field is not a literal | {badarg, Node} with the field's tree | {badarg, Form} with the field reverted to its erl_parse form | syntax_tools |
erl_prettypr
| Difference | OTP | Clause | Owner |
|---|---|---|---|
The text of an error or warning marker {Line, Module, Descriptor} whose module the program does not link | Module:format_error/1 loads the module and formats the descriptor | The marker's term, as when format_error/1 fails | syntax_tools |
erl_syntax_lib
| Difference | OTP | Clause | Owner |
|---|---|---|---|
limit/2,3 with a depth that is not an integer | Compares the depth with integers in term order (an atom depth keeps a leaf) | badarg | syntax_tools |
prettypr
| Difference | OTP | Clause | Owner |
|---|---|---|---|
A paper or ribbon width that is not an integer (prettypr:format(D, wide)) | Used only when compared or subtracted: a short document lays out, a longer one raises badarith or compares by term order | function_clause | builtins |
prettypr:sep([]) or par([]) laid out | case_clause | function_clause | builtins |
erl_lint
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Behaviours named by -behaviour | Any module on the code path answers behaviour_info/1 | Only modules linked into the program answer; another is undefined_behaviour | erl_lint |
| Order of several errors found in one map fold (singleton type variables of one type, specs without a function, callback checks) | The map's internal order (atoms by index) | Term order | erl_lint |
erlang:is_builtin/3 for -deprecated, -removed and -unsafe entries that are not exported | OTP's BIF table | The runtime's builtins, which include native library functions | erl_lint |
is_guard_expr/1 of a record construction | Raises case_clause | Answers whether the fields are guard expressions | erl_lint |
epp
| Difference | OTP | Clause | Owner |
|---|---|---|---|
-include_lib("App/...") not on the include path | Found in code:lib_dir(App), which knows OTP's applications | Found in code:lib_dir(App) of Clause's code path, which has no OTP applications unless they are added to it | library |
erl_pp
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Shape of the characters erl_pp returns | Deep character lists | One flat character list; flattened output is equal | builtins |
qlc
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Stack trace of an exception raised in a cursor process, as next_answers/1,2 re-raises it | The cursor's stack trace followed by the owner's process_info(self(), current_stacktrace) | Followed by the stack trace of an exception caught in the owner (Clause has no current_stacktrace item) | qlc |
qlc:q/1,2 in erl_eval, ets:table/1,2, dets:table/1,2 in a program that never names qlc | qlc is always loaded | undef for qlc: these reach it by a runtime name, so only programs naming qlc (any module including qlc.hrl does) link it | library |
Annotation numbers in the code of compiled queries (info/2 with {format, abstract_code}) | OTP's transform numbers annotations around every integer of the module, the characters of qlc.hrl's path in its file attribute included | The same transform, but Clause's qlc.hrl has another path, so numbers past the first query may differ; the code is otherwise equal | qlc |
qlc_pt
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Messages of qlc_pt:parse_transform/2 at run time | From compile:noenv_forms/2 (strong_validation): erl_lint plus the compiler's core passes, such as sys_core_fold warnings | From erl_lint only, as OTP's qlc_pt does when the compiler is not available | qlc_pt |
Interactive shell
| Difference | OTP | Clause | Owner |
|---|---|---|---|
| Line editing in a terminal | edlin: cursor keys, line history, search, tab completion over edlin_expand, the ..> multiline prompt | The terminal's own (cooked) line editing; lines are read as the terminal delivers them, with the same prompt for each further line of a command, as OTP does for piped input | library |
| Job control | Ctrl+G opens the JCL menu (new, connected, interrupted and killed shells) | None; the banner and the slow-command hint still name Ctrl+G as OTP prints them | library |
exit(). when the shell reads piped input | A new shell starts and reads nothing more | A new shell starts and reads the rest of the input | library |
Programs that start the shell without --shell | Every module of the code path is callable | Only the modules the program links; without --keep-exports dynamic calls reach only exports the program can reach | library |
Records of compiled modules (rr(Module), shell_default and user_default at start) | Read from the module's debug information or source | {error, nofile}: linked code keeps no abstract code; rr/1,2,3 of source and .beam files read them | library |
save_module/1 | Writes the module and compiles it with c:c/1 | Writes the module only (no compiler) | library |
edlin_expand:expand/1,2 inside a call or a record field value | Function heads and argument types from the function's specification (lists:seq(From, To)), atoms and types the specification allows | Mod:Fun/Arity heads of the exported arities, no type suggestions: linked code keeps no specifications | library |
edlin_expand:expand/1,2 of functions and modules | Functions grouped by the documentation's groups; modules of the code path | One Functions group (no documentation); the program's modules | library |
c:i/0,1, c:ni/0, c:regs/0 columns | Each process's initial call and current function | proc_lib's initial call or undefined; the current function undefined (no current_function item) | library |
Random numbers
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Strip heights of rand's normal ziggurat | Tabled constants | Computed to the nearest double from the ziggurat's constants; a height may differ in its last bit, which changes a slow-path accept only for a draw within that bit of the bound (odds near 2^-53 per slow-path draw). Strip widths and bounds are observed from OTP and equal | builtins |
crypto
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Order of crypto:supports(ciphers) | The NIF's table sorted by atom index, which varies between VM runs, then the aliases | OTP's table order, then the aliases | builtins |
{File, Line} of crypto's {badarg | notsup | error, {File, Line}, Description} errors | OTP's C source file and line ({"hash.c", 111}) | Clause's C++ source file and line ({"hash.cpp", 63}); the class and description are OTP's | builtins |
link_type of crypto:info/0 | How OTP linked libcrypto (usually dynamic) | Always static, also when an installed shared libcrypto is linked | OpenSSL |
Long pbkdf2_hmac/5 computations (more than 100 iterations or 64 bytes) | Run on a dirty CPU scheduler | Run on the calling process's scheduler worker, which runs no other process meanwhile | builtins |
modules of the crypto application | [crypto, crypto_ec_curves] | [crypto] | library |
Long crypto_one_time/4,5 and crypto_one_time_aead/4,6,7 texts (more than 20000 bytes) | Run on a dirty CPU scheduler | Run on the calling process's scheduler worker, which runs no other process meanwhile | builtins |
The fips_mode application variable of crypto | Read when the crypto module loads: true enables FIPS mode | Not read (Clause has no on_load and programs have no sys.config): use crypto:enable_fips_mode/1, or clau --fips | crypto |
crypto:info_fips/0 without a loadable FIPS provider | not_enabled when OTP was built with FIPS support, not_supported otherwise | not_supported | crypto |
When the crypto application is loaded | When the crypto module loads (its on_load) | When a crypto_cache, crypto_aes or crypto_prng1 generator first reads rand_cache_size | library |
RSA key generation (crypto:generate_key(rsa, ...)) and SLH-DSA signatures | Run on a dirty CPU scheduler | Run on the calling process's scheduler worker, which runs no other process meanwhile | builtins |
The rsa_sslv23_padding option of crypto:public_encrypt/4 and its siblings | Accepted when OTP is built against an OpenSSL that has SSLv23 padding (1.1, 3.x) | "Bad padding type in option rsa_padding", as OTP built against OpenSSL 4 | builtins |
crypto:ec_curve/1 of a curve the linked libcrypto lacks (an OpenSSL built without binary curves) | The parameters from crypto_ec_curves | error:function_clause, as for an unknown curve: the parameters come from the libcrypto's built-in curves | builtins |
beam_lib decryption of debug information in a program that does not call crypto | crypto is always present | key_missing_or_invalid unless the program links crypto (--with-crypto), since beam_lib calls it dynamically | library |
ASN.1
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Stack in {error, {asn1, {Reason, Stack}}} | Frames of the generated enc_*/dec_* functions and asn1rtt_* | Frames of Module:encode/2 or decode/2 and the codec builtin | asn1 |
| Functions of a generated module | Also exports enc_Type/1,2, dec_Type/1,2 and the other per-type functions asn1ct generates | Only encode/2, decode/2, info/0, the option functions and the value functions | asn1 |
A CHOICE whose body starts with ... (no root alternatives) | asn1ct reports a syntax error | Accepted | asn1 |
Value macros and functions that name another value inside an open type (PKCS-1's mgf1SHA1, rSASSA-PSS-Default-Identifier) | asn1ct leaves {'Externalvaluereference', Line, Module, Name} in place of the value | The referenced value | asn1 |
A runtime error (not an ASN.1 one) while public_key decodes or encodes an extension value | Raised as it happens, mostly error class, from the generated dec_ext-*/enc_ext-* function | exit({error, {asn1, Reason}}): the value goes through the generated module's decode/2 or encode/2, which wraps every failure | public_key |
The default Diffie-Hellman groups of public_key:dh_gex_group/4 | The groups OTP generated (lib/public_key/priv/moduli), 60–81 per size | Eight per size from OpenSSH's moduli file; same sizes (2047 to 8191) | public_key |
Frames of a public_key exception | public_key, pubkey_pem, pubkey_pbe, pubkey_cert_records, pubkey_cert, pubkey_crl, pubkey_policy_tree, pubkey_ocsp, pubkey_os_cacerts, pubkey_ssh | public_key, clause_public_key_codec, clause_public_key_keys, clause_public_key_cert, clause_public_key_path, clause_public_key_crl, clause_public_key_ocsp, clause_public_key_test_data and clause_public_key_cacerts | public_key |
Unsupported features
| Difference | OTP | Clause | Owner |
|---|---|---|---|
erl_ddll:loaded_drivers/0 | The linked-in drivers (tcp_inet, udp_inet, ram_file_drv, ...) | {ok, []}: Clause's ports are not drivers | library |
erl_ddll:load/2, load_driver/2, try_load/3 | Load a driver, or {error, {open_error, Code}} for a missing file | {error, no_ddll_available}, OTP's answer without DDLL functionality | library |
erl_ddll:format_error({open_error, Code}) for an operating system code (Code =< -10) | The system's message ("The specified module could not be found.") | "Unspecified error" | library |
erl_epmd:names/1, port_please/2,3 | Ask a running epmd daemon | Answer as without one ({error, address}, noport or the configured listen port); start_link/0 is declined | library |
erl_boot_server | A working boot server for diskless nodes | Every function is declined | library |
peer:start/1, start_link/1 with an alternative connection | Start a peer node over standard I/O or TCP | error:notsup after OTP's option checks: Clause cannot run another node | library |
pool's expected loads | A statistic collector per node updates them every few seconds | Only get_node/0 and pspawn/3 raise them | library |
ssl
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Atoms ssl:str_to_suite/1 takes as existing | Any atom of the VM's atom table | The program's atoms and the parts of the suite table | ssl |
ssl:str_to_suite/1 of chardata that is not a flat string (["TLS_AES_128_GCM_SHA256"]) | string functions may parse it | {error, {not_recognized, Name}} | ssl |
The ssl application's start module and supervision tree | ssl_app; ssl_sup with ssl_admin_sup, ssl_pem_cache, ssl_manager, tls_sup and dtls_sup | clause_ssl_app; ssl_sup with ssl_pem_cache, ssl_manager (the client session cache only), tls_connection_sup, whose children are the connection processes, and ssl_unknown_listener once an upgraded server socket needs it; listening sockets' session caches are not supervised | ssl |
Frames of an ssl exception | ssl, ssl_cipher, ssl_cipher_format, tls_v1, tls_record, dtls_record | ssl and clause_ssl_algorithms | ssl |
Text of a {tls_alert, {Description, Text}} error and alert log entries | `"TLS client: In state certify at ssl_handshake.erl:2304 generated CLIENT ALERT: Fatal - Unknown CA | ||
| "`: the state of OTP's state machine and a source location | `"TLS client: In state certify generated CLIENT ALERT: Fatal - Unknown CA | ||
": helloorcertifyduring the handshake,connection` after it, no location | ssl | ||
ssl connection processes | A tls_dyn_connection_sup per connection with a receiver (gen_statem) and a sender process; the sslsocket record names both and an ETS table | One clause_ssl_connection (gen_server) per connection; the record names it twice and no table | ssl |
ssl:connection_information/1 | The negotiated items and every ssl option of the connection | The negotiated items (protocol, session_id, session_data, session_resumption, selected_cipher_suite, selected_group, sni_hostname) and the main options (verify, versions, ciphers, cacerts, depth, ...) | ssl |
session_data of ssl:connection_information/1,2 | term_to_binary/1 of OTP's internal #session{} record | libssl's DER encoding of the session; each implementation reads only its own | ssl |
Ticket maps of {session_tickets, manual} ({ssl, session_ticket, Ticket}) | cipher_suite, sni, psk, timestamp and ticket (OTP's internal #new_session_ticket{} record) | cipher_suite, sni, timestamp and ticket (libssl's DER session); each implementation reads only its own | ssl |
{use_ticket, Tickets} with several tickets | Offers all of them | Offers the first whose suite the client offers (libssl resumes one session) | ssl |
A stateless server's anti_replay | Bloom filters (with false positives); a replayed ClientHello gets a full handshake | Exact sets of PSK binders per window, no false positives; a replayed ClientHello resumes without its early data | ssl |
Client early_data larger than the ticket's limit | OTP 29.1.1 sends it and the server fails with unexpected_message | Not sent; the owner gets {early_data, rejected} | ssl |
| A stateful server given a used ticket with early data | OTP 29.1.1 fails the handshake with unexpected_message | A full handshake; the early data is rejected | ssl |
| ECDHE_PSK suites with AES-GCM or AES-CCM (RFC 8442) | Negotiated | Never negotiated: libssl lacks them | ssl |
SRP suites and srp_identity | Negotiated over crypto's SRP | Never negotiated: libssl's SRP interface is deprecated | ssl |
| A PSK client whose key differs from the server's | The server's bad_record_mac alert is not read; the client sees {error, closed} | The client reads the alert: {error, {tls_alert, {bad_record_mac, _}}} | ssl |
ssl_crl_cache with {http, Timeout} | Fetches missing CRLs from their http distribution points with httpc | Never fetches: Clause has no inets; the hidden helpers is_internal_ip/1, validate_host/4 and is_allowed/3 raise not_implemented | ssl |
log_level debug and info of ssl connections | Protocol traces of records and handshake messages | None; alerts are logged as OTP's | ssl |
key_update_at after a KeyUpdate the peer asked for | The answering KeyUpdate starts a new count of bytes | The count goes on until the connection's own KeyUpdate | ssl |
How often sni_fun is called for a name it answers with undefined | Twice in a handshake | Once | ssl |
Groups, signature algorithms and early data of sni_hosts and sni_fun options | Applied | The listening socket's stay (libssl keeps them per connection) | ssl |
Sessions given to ssl_session_cache_api modules | OTP's internal #session{} records | Maps (session_id, data, cipher_suite, own_certificate, peer_certificate, sni, time_stamp, ...); both are opaque to the module | ssl |
Emulated options (active, mode, packet, header) set on a listening ssl socket | Kept by its option tracker for the next accepted connections | Ignored: accepted connections take the options listen/2 was given | ssl |
| A server linked with libssl older than 4.1 that verifies client certificates | Rejects a chain during the handshake with the chosen alert | Verifies after libssl's handshake; a rejection closes the connection without the alert | ssl |
ssl:handshake/3 with options on an accepted socket whose handshake pauses at the hello | {ok, Extensions}, the extensions still OTP's internal records and no socket | {ok, SslSocket, Extensions}, as documented and as handshake/1,2 return it | ssl |
The hello messages an ssl connection sends | OTP's: its extensions, groups and key shares | libssl's: for example a ClientHello has renegotiation_info and no signature_algs_cert, so a peer paused at the hello sees other extensions | ssl |
Calls other than handshake_continue and handshake_cancel on a connection paused at the hello | Wait until the handshake goes on | {error, closed} | ssl |
ssl:close/1 and close/2 with a timeout | After close_notify, shut the transport down for writing and wait up to 5 s (not the given timeout) for the peer to close | Close the transport right after close_notify | ssl |
A TLS 1.2 renegotiation the peer refuses (no_renegotiation) | ssl:renegotiate/1 gives {error, renegotiation_rejected} and the connection goes on | The same result, but libssl then ends the connection with a handshake_failure alert | ssl |
ssl:update_keys/2 on a TLS 1.2 connection | The connection's sender process crashes (function_clause) and the caller exits | {error, not_supported}, the connection goes on | ssl |
ssl:export_key_materials/4,5 with a length of 0 or less | The connection hangs | A negative length is {error, bad_input} (TLS 1.3) or {error, badarg} (TLS 1.2); 0 gives libssl's exporter result | ssl |
master_secret of ssl:connection_information/2 on TLS 1.3 | The key schedule's master secret | Left out: libssl does not expose it | ssl |
Items of a {keylog_hs, Fun} when a TLS 1.3 client fails before the server's Finished (a rejected server certificate) | Both handshake traffic secrets | Only SERVER_HANDSHAKE_TRAFFIC_SECRET: libssl derives the client's own handshake secret after the server's Finished | ssl |
Key log lines of keylog and the keep_secrets funs | Deep character lists (io_lib:format/2 results) | Flat strings with the same characters | ssl |
Parse transforms
| Difference | OTP | Clause | Owner |
|---|---|---|---|
Name in the file attribute of an included file (--print-abstr, transform input) | The include directory joined to the name, as found (inc.hrl, /abs/dir/inc.hrl, ../inc/x.hrl) | The path relative to the working directory when the file is under it, else the absolute path | transforms |
Line 0 annotations of imported forms (generated code, as from the qlc transform) | A valid line: the code has no source line | The code takes the place of the node imported before it | transforms |
generated annotations of imported forms (.abstr inputs, transform output) | erl_lint and the compiler skip some warnings for generated code | Ignored: the code is checked like source | transforms |
{error, {Location, Module, Descriptor}} forms in .abstr inputs | Formatted with Module:format_error/1 | The descriptor's term text; transform output is formatted by the loader | transforms |
| Preprocessor and parse problems a parse transform gets as forms | epp's and erl_parse's descriptors, each in place of the form that failed | {error, {Location, clause_source, Text}} with Clause's message, all before eof | transforms |
| Compile options a transform receives | erlc's full option list | {features, _}, report_warnings, report_errors, {cwd, _}, {outdir, _} (the working directory), {i, _}, {d, ...} and {parse_transform, _} | transforms |
Clause