►
Description
Compile-Time Social Coordination by Zac Burns
You can write good code. So can I. But can we write correct code together? The hardest problem facing the ordinary developer of today is not in algorithms or frameworks. Bugs are commonly found between the lines. Projects contain rules that must be adhered to everywhere but are specified nowhere. They are conventions, tribal knowledge, and best practices. Let's learn how Rust makes it easier to write code that agrees and is consistent across files, crates, and persons. This is the story of how I stopped stepping on everyone's toes and learned to love the borrow checker.
A
A
A
Each
scenario
came
with
its
own
unique
problems,
but
in
every
environment
the
same
issue
has
come
up
over
and
over
again,
that
issue
was
maintaining
consistency
in
code
written
by
different
people
and
at
different
times
a
well-architected
well-implemented
program
is
internally
consistent.
There
are
patterns
and
design
choices
that
are
adhered
to
in
remote
locations
across
the
code.
A
A
A
There's
no
obvious
problem
with
either
the
poison
or
missile
component
taken
individually.
Only
by
looking
at
both
simultaneously
can
we
see
that
each
makes
different
assumptions
about
the
system.
They
belong
to.
One
locks,
the
entity
before
modifying
its
state
and
the
other
does
not,
which
one
is
correct.
A
This
is
another
trick.
Question
no
component
by
itself
can
be
either
correct
or
incorrect.
The
whole
program
is
only
correct
if
every
component
agrees
with
the
same
set
of
assumptions
as
every
other
component,
the
poison
component
may
assume.
The
system
has
two
copies
of
the
state.
One
copy
for
updating,
while
another
thread
looks
at
a
read-only
copy
for
rendering
the
missile
component
may
assume
that
the
system
allows
for
concurrent
entity
updates
the
larger
the
program,
the
more
chances
there
are
for
inconsistencies,
since
any
line
could
create
an
inconsistency
with
any
other
line.
A
A
A
Cease
development
read
all
27
million
lines
of
the
program
and
its
dependencies
after
a
month
when
you
fully
understand
how
each
line
interacts
with
every
other
line,
make
a
small
change
and
hope.
Nobody
else
touched
anything
over
that
time.
Repeat
until
you
go
out
of
business,
I'm
being
sarcastic.
What
are
some
practical
things?
We
do.
A
We
write
comments,
comments
help,
but
it's
not
enough.
The
architecture
is
spread
out
all
over
the
code
to
explain
how
every
line
conforms
with
the
overall
design
would
be
redundant.
So
people
don't
do
that
worse.
If
you're
writing
new
code,
any
relevant
comments
would
be
by
definition
somewhere
else.
Where
you
can't
see
them.
A
A
A
A
The
neglected
company
wiki
is
not
the
answer,
nor
is
code
review
I
could
go
on,
but
that's
not
the
point
here
is
the
point,
the
more
I
consider
the
problem
of
maintaining
consistency,
the
more
I'm
convinced
that
the
best
return
on
our
investment
as
a
discipline
is
in
the
compiler
and
in
compiler
aided
solutions,
unlike
communication
or
mentorship,
the
compiler
scales
to
any
team
size,
giving
personalized
advice
to
every
contributor
exactly
when
they
need
it.
Unlike
a
company
wiki,
the
compiler
cannot
be
ignored
or
out
of
date,
unlike
some
random
blog
posts
on
the
internet.
A
A
A
A
A
People
coming
from
these
languages
may
have
recently
spent
time,
debugging
a
forgotten
call
to
lock,
or
they
may
have
recently
stored
a
reference
to
data
protected
by
the
lock
to
use
later
on
the
ui
thread,
or
maybe
they
locked
the
wrong,
lock
or
forgot
to
release
the
lock.
None
of
these
bugs
are
possible
in
rest.
A
A
A
A
Even
if
your
claim
were
verified,
the
change
would
likely
be
rejected
in
review,
because
we
do
not
know
the
assertion
will
hold
in
the
future.
This
is
again
the
same
social
coordination
problem
playing
out
across
time,
because
the
social
coordination
problem
is
so
hard.
We've
become
accustomed
to
the
habit
of
engineering.
Sub-Optimal
software
to
avoid
making
mistakes,
we've
even
invented
entire
architectures
that
are
elaborate.
Kitty,
gloves,
hiding
the
important
part
of
the
problem
we
are
actually
trying
to
solve,
transforming
the
data.
A
Here
we
have
a
generic
serialization
function
in
hazard,
binding.
It
takes
a
collection
of
names
and
values
to
serialize
zips
them
together
to
form
a
property
collection
loops
over
each
property,
then
writes
a
tag
for
each
name,
followed
by
a
write
of
the
corresponding
value
to
the
file
where's.
The
bug
the
theme
of
this
talk
is
bugs
that
come
about
through
poor
social
coordination
bugs
that
don't
fit
on
one
screen.
A
A
A
A
They
set
up
their
schema
with
a
predetermined,
consistent
order,
just
like
the
docs
said
to,
and
they
write
tagged
property
names
and
values
just
like
they
are
supposed
to
so.
The
new
dev
tries
their
usage
of
hazard
serializer
and
writes
a
file,
but
when
they
take
a
look
in
the
app,
the
data
is
all
wrong.
A
A
Their
code
now
works,
there's
an
added
benefit,
but
there
are
fewer
requirements
for
calling
this
function,
so
the
bug
won't
be
hit
in
the
future
and
we
ship
yay
except
except
nobody,
looked
at
these
two
pieces
of
code.
At
the
same
time,
the
old
serialized
function
that
I
showed
you
at
the
start.
With
the
new
fixed
tag
function,
do
you
know
how
many
things
a
person
can
keep
in
their
head
at
once?
A
A
So
what's
the
problem?
Well,
if
you
look
at
both
sections
at
the
same
time,
you
can
see
that
we
are
iterating
over
a
list
while
modifying
it
oops
the
program
probably
won't
crash.
Instead,
it
will
write
garbage
data,
which
is
arguably
worse
at
no
point
in
time
was
this
serialized
function
and
the
new
tag
function
on
the
same
screen?
At
the
same
time,
people
changed
different
bits,
fixing
the
problems
they
were
aware
of
creating
local
consistencies,
but
global
inconsistency.
A
Remember
that
these
examples
are
simplified.
Real
code
bases
are
comprised
of
huge,
directed
graphs
of
function,
calls
being
mutated
concurrently
by
multiple
people.
Even
if
two
inconsistent
nodes
in
that
graph
were
just
a
few
hops
away,
there
could
be
hundreds
of
nodes
reachable
within
the
same
distance.
A
Finding
the
inconsistency
is
much
like
finding
the
needle
in
a
haystack.
If
you
don't
know
a
priori,
where
to
look
our
example
may
seem
contrived,
but
the
issue
is
common
enough:
that
a
google
search
for
the
phrase
don't
iterate
a
list
while
modifying
comes
up
with
over
50
million
results,
this
time-lapse
animation
shows
only
the
files
and
directory
structure
of
a
project
that
I
worked
on
at
the
graph
to
index
blockchain
data.
A
A
Avoiding
iterating
over
a
list,
while
modifying
it
in
hazard
laying
required
social
coordination,
but
it's
a
mistake
that
I've
never
made
in
rust,
even
when
working
with
other
people.
The
compiler
ensures
for
me
that
the
connected
nodes
in
our
call
graph
are
consistent
in
rust.
There
are
three
ways
to
pass
values:
you
can
use
a
shared
reference
to
t
a
shared
reference
is
immutable
unless
you
implement
special
protections
to
guard
against
the
problems
that
come
with
shared
mutability,
we
call
that
interior
mutability,
but
the
typical
shared
reference
is
read.
A
A
A
This
is
the
distilled
version
of
the
bug
here.
At
names.itter,
a
temporary
is
constructed
that
maintains
the
state
of
the
iterator.
The
iterator
holds
a
shared
reference
to
names,
but
on
the
following
line,
the
call
to
sort
takes
names
by
unique
reference.
It
is
a
contradiction
for
a
reference
to
be
both
unique
and
shared.
At
the
same
time,
contradictions
are
bugs.
A
What's
neat
here
is
that
rust
will
detect
this
contradiction
through
any
number
of
layers.
Instructs
and
function
calls
so
that,
even
if
the
code
is
not
simple
like
in
the
example,
it
will
not
compile
with
the
inconsistency
the
bug
we
introduced
in
a
serializer
cannot
occur
in
rust,
because
the
whole
program
would
fail
to
compile
we're
almost
done.
I
promise
to
show
you
how
to
use
the
compiler
to
enforce
your
own
rules.
That
would
otherwise
require
social
coordination.
A
A
Okay:
let's
go
first
set
up
the
situation
at
edge
and
node.
We
write
multi-threaded
web
servers
that
each
serve
thousands
of
requests
every
second.
These
servers
read
data
from
a
database.
The
database
has
a
connection
limit
and
connection
takes
time
to
set
up
so
to
avoid
going
over
the
limit
or
incurring
the
setup
cost
on
every
request.
We
use
a
connection
pool
one
day.
We
notice
that
a
server
instance
stops
serving
requests.
A
There's
no
warning
everything
stops:
cpu
usage,
flatlines,
there's,
no
disk
usage,
no
queries
are
served
and
if
we
restart
everything
is
okay
again
until
the
next
time
that
it
happens.
Why
note
that
at
this
point,
you're
looking
at
an
issue
that
is
going
to
be
difficult
to
debug,
it's
a
real
server
with
lots
of
code.
Customers
are
panicked
and
the
issue
only
occurs
once
every
few
days
under
vast
amounts
of
load,
there
is
no
stack
trace
in
the
logs
or
smoking
gun
of
any
kind.
A
A
A
The
problem
is
that,
when
admit
event
is
called,
we
are
already
holding
a
connection
from
the
connection
pool.
The
connection
held
is
not
returned
to
the
pool
until
it
is
dropped
after
emit
event
returns.
In
normal
circumstances,
this
is
okay.
The
second
connection
is
acquired
and
then
both
are
released.
First,
the
connection
and
emit
event
is
released.
A
Then
the
outer
connection
is
released,
but
rarely
if
50
requests
hit
emit
event
simultaneously,
they're
already
holding
the
limit
of
50
connections,
so
the
call
to
get
connection
within
a
minute
event
never
returns,
because
the
connection
pool
is
empty,
since
admit
event
never
returns.
None
of
the
outer
connections
are
returned
to
the
pool
and
the
whole
request
pipeline
is
deadlocked
across
all
threads.
A
In
order
to
understand
this
bug,
you
have
to
be
aware
of
very
specific
architectural
details.
You
have
to
know
that
connections
are
pooled.
You
have
to
know
that
events
go
through
the
database.
You
have
to
know
that
connections
return
to
the
pool
on
drop.
You
have
to
know
everything
that
those
disparate
details
infer
and
you
have
to
know
for
any
function
that
you
are
writing
that
no
caller
of
your
function
holds
a
connection.
If
any
call
you
might
make
would
attempt
to
acquire
one.
A
A
They
share
this
information
with
the
team
write.
A
blog
post
add
a
comment
to
get
connection
and
go
on
a
conquest
to
stamp
out
every
instance
of
this
bug
they
can
find.
But
this
bug
is
really
subtle.
It's
easy
to
miss,
even
when
you
know
what
to
look
for,
because
you
have
to
analyze.
Regions
of
a
directed
graph
of
function
calls.
A
A
A
They
may
be
unaware
that
upstream
of
connection
is
held
while
downstream
a
new
connection
is
obtained
because
from
where
the
edit
is
made,
they
may
see
neither
so
here's
this
bug
it's
hard
to
detect
easy
to
create,
is
not
fixable
via
architecture
and
hurts
users
in
production.
It's
time
for
compile
time
social
coordination.
A
The
solution
is
to
create
a
token,
representing
the
permission,
to
obtain
a
connection
from
the
pool
we
can
ensure.
This
permission
is
granted
once
per
request
by
making
the
token
constructor
private
getconnection
is
then
appended
to
take
a
unique
reference
to
the
token
what
that
does
is
to
tie
the
unique
loan
of
the
token
to
the
loan
of
the
connection
from
the
pool.
A
A
A
That's
it
a
dozen
lines
of
code
to
set
up
the
rules
and
the
graph
traversal
search
for
inconsistency
is
now
mechanically
executed
by
the
compiler,
removing
the
error
prone
and
easily
forgotten
work
from
the
developer
as
a
bonus.
The
token
is
removed
at
compile
time.
There
is
no
heap
allocation
or
any
runtime
cost
at
all.
A
The
second
is
that
they're
all
part
of
a
broader
class
of
problems
fixed
as
a
natural
consequence
of
the
borrow
checker,
in
fact,
many
other
social
coordination
problems
like
memory
management.
If
I
pass
a
pointer
to
your
library
whose
responsibility
is
it
to
free
that
memory
save
global
variables,
high
performance,
non-defensive
code,
security,
even
wagon
support,
are
all
underpinned
by
the
borrow
checker.
A
A
All
true,
but
none
of
these
I
consider
differentiators,
they
are
important,
but
they
are
literally
the
minimum
bar.
I
have
no
use
for
any
language
where
I
cannot
write
programs
with
excellent
runtime
performance
which
does
not
compile
to
the
platforms
I
care
about,
for
example,
among
the
small
set
of
languages
that
meets
this
minimum
standard.
I
ask
what
sets
them
apart.
A
A
A
The
whole
result
is
refreshing
because
there
is
a
single
unifying
concept
that
provides
a
benefit
across
almost
all
apis.
The
accumulation
of
many
small
wins
adds
up.
You
want
to
know
in
a
sentence.
What's
so
important
here
is
that
there
is
finally
a
language
that
both
has
a
string,
concatenation
method
and
I'm
not
afraid
to
use
it
at
the
risk
of
being
hyperbolic.
A
I
believe
that
the
borrow
checker
has
rendered
obsolete
much
of
the
knowledge
that
I've
gained
over
the
past
20
years,
and
I
think
we
haven't
even
seen
how
far
this
experiment
will
go,
suppose
that
the
future
of
programming
can
shed
defensive
architectural
patterns,
endless
debugging,
passing
on
best
practices
and
tribal
knowledge
manually
and
learn
to
love.
One
concept
that
of
lifetimes-
in
that
case,
we
will
see
farther
and
accomplish
more
than
our
predecessors,
if
you're
not
yet
using
rust.
That
is
the
trade-off
that
I
present
to
you.
The
choice
is
now
yours.