CraftConf 15–Takeaways from “Architecture Without an End State”

In enter­prises we tend to sell archi­tec­tural projects by show­ing you the cur­rent messy state and pitch you an ide­al­ized end state. But the prob­lem is that, the ideal end state we’re pitch­ing exists only as an idea, with­out hav­ing gone through all the com­pro­mises and organic growth that made the cur­rent state messy.

Once we have gone through the process of imple­ment­ing and grow­ing the sys­tem over the course of its life­time, how do we know we won’t end up in the same messy sit­u­a­tion again?

bsg-happened-before

The 3-year Plans

Worse still, these pitches are always fol­lowed by a 3 year plan where you see no return on your invest­ment for 2 whole years!

3-year-plan

And since 3 years is a long time, many things can hap­pen along the way - merg­ers, acqui­si­tions, new tech­nolo­gies, new CTO/CIO, etc. — and often we don’t ever real­ize the orig­i­nal 3 year plan before these forces of change takes us to a new direction.

And the vicious cycle con­tin­ues.…
3-year-plans

 

Within a large orga­ni­za­tion there are always mul­ti­ple forces of change hap­pen­ing at the same time, and your per­spec­tive changes depend­ing on your posi­tion within the organization.

In a com­plex sociotech­ni­cal sys­tem like this, there’s no priv­i­leged van­tage point. One per­son can­not under­stand all ram­i­fi­ca­tions of their deci­sions, not even the CEO of a company.

The best you can do, is locally con­tex­tual actions.

 

8 Rules for Survival

1. Embrace plurality

Avoid ten­dency for the “one”, e.g.

  • sin­gle source of truth;
  • sin­gle sys­tem of record;
  • one sys­tem to rule them all;
  • etc.

The prob­lem with hav­ing a sin­gle sys­tem of records (SSOR) is that it’s ter­ri­bly frag­ile. Many real-world events (e.g. M&A, divesti­tures, enter­ing new mar­ket) can eas­ily vio­late your pur­suit for the “one” system.

In epis­te­mol­ogy (i.e. the study of “how we know what we know”), there’s the idea that we can only know what we are able to record in a sys­tem. If your SSOR is badly shaped for the infor­ma­tion that exist, you might be unaware of the rest of the instances of infor­ma­tion you’re look­ing for.

 

When mod­el­ling a domain, some­times it’s even tough to find “one” def­i­n­i­tion for some­thing that every­one can agree on.

For exam­ple, a “cus­tomer” can mean dif­fer­ent things to dif­fer­ent departments:

  • for cus­tomer ser­vice, a cus­tomer is some­one who is pay­ing you money
  • for sales, some­one is a cus­tomer as soon as you start talk­ing to them

 

Finally, there’s this philo­soph­i­cal idea that, the model you use to view the world shapes the thoughts you are able to think.

Your par­a­digm defines the ques­tions you can ask.

ludwig-wittgenstein

I think this also applies to pro­gram­ming lan­guages and par­a­digms, with the same detri­men­tal effects in lim­it­ing our poten­tial as cre­ative prob­lem solvers. And it’s a good rea­son why you should learn at least a few pro­gram­ming par­a­digms in order to expand your mind.


 

When deal­ing with mul­ti­ple sys­tems of records, we change our empha­sis and instead focus on:

  • who’s the author­ity for what span?
  • define a sys­tem to find the author­ity for a span given some unique iden­ti­fier, e.g. URIs, URNs (AWS’s ARN sys­tem springs to mind)
  • agree­ing on a rep­re­sen­ta­tion so we can inter­change them across dif­fer­ent systems
  • enable copies and have a mech­a­nism for synchronization

Some­thing inter­est­ing hap­pens when you do these — your infor­ma­tion con­sumers no longer assume where the infor­ma­tion comes from.

If you pro­vide full URLs with your prod­uct details, then the con­sumers can go to any of the URLs for your prod­uct infor­ma­tion. They must become more flex­i­ble and assume there is an open world of infor­ma­tion out there.

Along with the infor­ma­tion you get, you also need a mech­a­nism for deter­min­ing what actions you can per­form with them. These actions can be encoded as URLs and may point to dif­fer­ent services.


As I lis­tened, two words sprang to mind:

microser­vices - break­ing up a big, com­plex sys­tem into smaller parts each with author­ity over its own span; infor­ma­tion is accessed through a sys­tem of iden­ti­fiers and an agreed-upon for­mat for inter­change (rather than shared data­base, etc.)

HATEOAS — or REST APIs in the way Roy Field­ing orig­i­nally described; actions per­mit­ted on infor­ma­tion is pass back along­side the infor­ma­tion itself and encoded as URLs

 

2. Con­tex­tu­al­ize Downstream

We have a ten­dency to put too much in the data we send down­stream, which can often end up being a very large set.

But our busi­ness rules are usu­ally contextual:

  • not all infor­ma­tion about an entity is required in every case
  • oper­a­tions are not avail­able across entire classes of entities

Again, HATEOAS strikes me as a par­tic­u­larly good fit here for con­tex­tu­al­iz­ing the data we send to down­stream systems.


We build sys­tems as a net­work of data-flows, but we also need to look at the ecosys­tem of sys­tem of sys­tems and be aware of the down­stream impacts of our changes.

Some­thing like the fol­low­ing is obvi­ously not desirable!

downstream-impact

So the rule is:

  • Aug­ment the data upstream, add fields but not rela­tion­ships if you can avoid it.
  • Con­tex­tu­al­ize down­stream, apply your busi­ness rules as close to the user as pos­si­ble. Because as it turns out, the rules that are clos­est to the users change the most often.
  • Min­i­mize the enti­ties that all sys­tems need to know about.

 

3. Beware Grandiosity

Fight the temp­ta­tion to build the model to end all mod­els (again, embrace plu­ral­ity), aka Enter­prise Mod­el­ling Dic­tio­nary.

The prob­lem with an Enter­prise Mod­el­ling project is that, it requires:

  • a global per­spec­tive, which we already said is not avail­able in a com­plex system;
  • agree­ment across all busi­ness units, which is extra­or­di­nar­ily hard to achieve;
  • tal­ent for abstrac­tion; but also
  • con­crete expe­ri­ence in all contexts;
  • a small enough team to make decisions.

Such teams only exist in Hol­ly­wood movies..

TheATeam-77904-4

Instead, look to how stan­dard com­mit­tees work:

  • they com­pro­mises a lot;
  • they don’t seek agree­ments across the board, and instead agree on small sub­sets and leave room for ven­dor ventures;
  • they take a long time to converge
  • they delib­er­ately limit their scope

All mod­els are wrong, but some are useful.”

- George Box

So the rule is:

  • don’t try to model the whole world;
  • find com­pro­mises and sub­sets that every­one can agree on;
  • start small and incre­men­tally expand;
  • assume you don’t know every­thing and leave room for extension;
  • allow very lengthy com­ment period (with plenty of email reminders since every­one leaves it till the last minute..)

 

4. Decen­tral­ize

Decen­tral­iza­tion allows more explo­ration of mar­ket space because it allows mul­ti­ple groups to work on dif­fer­ent things at once.

You can also explore the solu­tion space bet­ter because dif­fer­ent groups can try dif­fer­ent solu­tions for sim­i­lar problems.

How­ever, you do need some pre­req­ui­sites for this to work successfully:

  • Trans­parency — meth­ods, work and results must be visible
  • Iso­la­tion — one group’s fail­ure can­not cause wide­spread damage
  • Eco­nom­ics — cre­ate a frame­work for every­one to make deci­sions that con­tribute towards the larger goal

The Boe­ing 777 project is an excel­lent exam­ple of dis­trib­uted eco­nomic decision-making.

The no. 1 thing that deter­mines a plane’s life­time cost is the weight of the air­craft. So the weight of the air­craft is in direct trade-off with cost (e.g. lighter mate­ri­als = more expen­sive man­u­fac­tur­ing cost).

They worked out what the trade-off was and set out rules so that an engi­neer can buy a pound of weight for $300 — i.e. as an engi­neer, you can make a deci­sion that’ll shave a pound of weight off the air­craft at the expense of $300 of man­u­fac­tur­ing cost with­out ask­ing anybody.

Engi­neer­ing and pro­gramme man­agers and above have a big­ger man­u­fac­tur­ing cost budge per pound, and any­thing above these pre­de­fined lim­its requires you to go before the gov­er­nance board and jus­tice your decisions.

What they have done is set­ting the trade-off poli­cies cen­trally and by defin­ing the bal­anc­ing forces glob­ally they have cre­ated a frame­work that allows deci­sions to be made locally with­out requir­ing reviews.

In IT, Michael has observed that cen­tral­iza­tion tends to lead to bud­get fights between depart­ments. Whereas decen­tral­iza­tion allows for sliv­ers of bud­gets work­ing towards a com­mon goal.

 

5. Iso­late Fail­ure Domains

If you reverse the direc­tion data flows through your sys­tems and you can find the direc­tion of depen­den­cies between them.

dir-dataflow-and-dependencies

And the thing about depen­dency is that some­times you can’t depend on them. So you def­i­nitely want to iso­late your­self from upstream depen­den­cies. In his book, Release It!, Michael out­lined a num­ber of use­ful pat­terns to help you do just that, e.g. cir­cuit breaker, bulk­heads, time­out,  fail fast. Many of these pat­terns have been incor­po­rated in libraries such as Netflix’s Hys­trix.

 

Another kind of fail­ure is when you have missed a solu­tion space entirely. This is where mod­u­lar­ity can give you value in that it gives you options that you might exer­cise in future.

Just as options in the finan­cial sense (a long call in this case), there’s a neg­a­tive pay­off at the start as you need to do some work to cre­ate the option. But if you choose to exer­cise the option in the future then there’s a long pos­i­tive payoff.

value-of-option

Whilst Michael didn’t spell it out, I think the value of mod­u­lar­ity in this case lies in the abil­ity to recover from hav­ing missed a solu­tion space the first time around. If the mod­u­lar­ity is there then at least you have to option to adopt a dif­fer­ent solu­tion at a later point (i.e. exer­cis­ing the option).


Michael then gave an exam­ple of a trad­ing com­pany who has build their sys­tem as many small appli­ca­tions inte­grated by mes­sag­ing. The appli­ca­tions are small enough and fine grained enough that it’s cheaper to rewrite than to main­tain, so they treat these appli­ca­tions as dis­pos­able components.


I’d like to give a shout out to Greg Young’s recent talk, The Art of Destroy­ing Soft­ware, where he talked at length about build­ing sys­tems whilst opti­miz­ing for deletabil­ity. That is, you should build sys­tems so that they’re never big­ger than what you can rea­son­ably rewrite in a week. And that is as good a guide­line on “how big should my microser­vice be?” as I have heard so far.

 

6 & 7. Data Out­lives Appli­ca­tions; Appli­ca­tions Out­lives Integrations

As things change, a typ­i­cal lay­ered archi­tec­ture breaks when there are pres­sure from either side.

layered-architecture-breaks

A Hexag­o­nal archi­tec­ture, or Ports and Adapters style archi­tec­ture on the other hand, puts your domain in the cen­tre rather than the bot­tom of the stack. The domain is then con­nected to the out­side world via adapters that bridges between the con­cepts of that adapter and the con­cepts of the domain.

hexagonal-architecture

Hexag­o­nal archi­tec­tures make tech­nol­ogy tran­si­tion more of a bound­ary layer issue than a domain layer issue, and allows you to explic­itly rep­re­sent mul­ti­ple adapters from the same domain for dif­fer­ent purposes.

 

8. Increase Discoverability

In large orga­ni­za­tions you often find peo­ple work­ing on projects with sig­nif­i­cant amounts of over­lap due to lack of dis­cov­er­abil­ity about what other peo­ple are work­ing on.

Just as one would search Github or Google to see if a sim­i­lar project exists already before embark­ing on his/her own open source alter­na­tive, we should be doing more to enable dis­cov­er­abil­ity within companies.

Doing so allows us to move faster by build­ing on top of oth­ers work, but in order to do this we need to make our work vis­i­ble internally:

  • inter­nal blogs
  • open, eas­ily acces­si­ble code repositories
  • mod­ern search engine

Inter­nal teams should work like open source projects whilst retain­ing a prod­uct mentality.

One thing to watch out for is the bud­get cul­ture where:

  • con­tri­bu­tions to other people’s projects are not wel­comed due to bud­get fights;
  • inquiries about each other’s work are alarm­ing and con­sid­ered pre­cur­sor to their attempt to suck your bud­get into theirs

Instead you should embrace an engi­neer­ing cul­ture which tend to be much more open. You could start to build up an engi­neer­ing cul­ture by:

  • hav­ing team blogs
  • allow users to report bugs
  • make CI servers public

 

 

Finally, stop chas­ing ide­al­ized end state, and embrace the wave of change.

 

I was at QCon Lon­don ear­lier this year, and in Kevlin Henney’s Small is Beau­ti­ful talk he deliv­ered a sim­i­lar mes­sage. That we should think of soft­ware as prod­ucts, not projects.  If soft­ware are projects then they should have well-defined end state, but most often, soft­ware do not have well-defined end state, but rather evolved con­tin­u­ously for as long as it remains desir­able and purposeful.

 

 

Links

 

 

Understanding homoiconicity through Clojure macros

clojure_homoiconicity

Hav­ing been pri­mar­ily involved with .Net lan­guages in my career so far, homoiconic­ity was a new idea to me when I first encoun­tered it in Clo­jure (and also later in Elixir).

If you look it up on wikipedia, you’ll find the usual wordy def­i­n­i­tion that vaguely makes sense.

…homoiconic­ity is a prop­erty of some pro­gram­ming lan­guages in which the pro­gram struc­ture is sim­i­lar to its syn­tax, and there­fore the program’s inter­nal rep­re­sen­ta­tion can be inferred by read­ing the text’s layout…”

so, in short: code is data, data is code.

 

quote & eval

Take a sim­ple example:

image

this line of code per­forms a tem­po­rary bind­ing (binds x to the value 1) and then incre­ments x to give the return value of 2.

So it’s code that can be exe­cuted and yields some data.

But equally, it can also be thought of as a list with three elements:

  • a sym­bol named let;
  • a vec­tor with two ele­ments – a sym­bol named x, and an integer;
  • a list with two ele­ments – a sym­bol named inc, and a sym­bol named x.

quote

You can use the quote func­tion to take some Clo­jure code and instead of eval­u­at­ing it, return it as data.

image

side­bar: any F# devel­oper read­ing this will notice the sim­i­lar­ity to code quo­ta­tions in F#, although the rep­re­sen­ta­tion you get back is not nearly as easy to manip­u­late nor is there a built-in way to eval­u­ate it. That said, you do have some options, including:

 

eval

On the flip side, you have the eval func­tion. It takes data and exe­cutes it as code.

image

After you have cap­tured some exe­cutable code as data, you can also manip­u­late it before exe­cut­ing the trans­formed code. This is where macros come in.

 

Macros

clojure.test for instance, is a unit test frame­work writ­ten with macros. You can do a sim­ple asser­tion using the ‘is’ macro:

image

and con­trast this with the error mes­sage we get from, say, NUnit.

image

Isn’t it great that the fail­ing expres­sions are printed out so you can straight away see what was wrong? It’s much more infor­ma­tive than the generic mes­sage we get from NUnit, which forces us to dig around and fig­ure out which line of the test failed.

Update 23/05/2015:

As Vasily pointed out in the com­ments, there is an asser­tion library for F# called Unquote which uses F# code quo­ta­tions (men­tioned above) and pro­duces user-friendly error mes­sages sim­i­lar to clojure.testIt goes to show that, even with­out macros, just being able to eas­ily cap­ture code as data struc­tures in your lan­guage can enable many use cases — Phil Trelford’s Foq mock­ing library is another good example.

 

Build­ing an assert-equals macro

As a process of dis­cov­ery, let’s see how this can be done via macros.

 

Ver­sion 1

To start off, we will define the sim­plest macro that might work:

image

oops, so that last case didn’t work.

That’s because the actual and expected val­ues passed into the macro are code, not the inte­ger value 2.

image

 

Ver­sion 2

So what if we just throw an eval in there?

image

that works, right? right?

Well, not quite.

Instead of manip­u­lat­ing the data rep­re­sent­ing our code, we have eval­u­ated them at com­pile time (macros runs at com­pile time).

You can ver­ify this by using macroex­pand:

image

so you can see that our macro has trans­formed the input code into the boolean value true and returned it as code.

 

Ver­sion 3

What we ought to do is return the code we want to exe­cute as data, which we know how to do already – using the quote func­tion. In the returned code, we also need to error when the asser­tion fails.

So start­ing with the code we want to exe­cute given that:

image

well, we’d want to:

  • com­pare the eval­u­ated val­ues of actual and expected and throw an Asser­tion­Error if they are not equal
  • dis­play the actual expres­sion (inc 1)  and expected expres­sion (+ 0 1) in the error message
  • dis­play the eval­u­ated value for actual — 2

so some­thing along the lines of the fol­low­ing, perhaps?

image

Now that we know our endgame we can work back­wards to define our macro:

image

See the resem­blance? The impor­tant thing to note here is that we have quoted the whole let block (via the ‘ short­hand). But in order to ref­er­ence the actual and expected expres­sions and return them as they are, i.e. (inc 1), (+ 0 1) , we had to selec­tively unquote cer­tain things using the ~ operator.

You can expand the macro and see that it’s seman­ti­cally iden­ti­cal to the code that we wanted to output:

image

Before we move on, you might be won­der­ing about some of the quote-unquote, unquote-quote actions going on here, so let’s spend a few moments to dwell into them.

Out­putting the actual expres­sion to be evaluated

Remem­ber, the actual and expected argu­ments in our def­macro block are the quoted ver­sions of (inc 1) and (+ 0 1).

We want to eval­u­ate actual only once for effi­ciency, and in case it causes side effects. Which is why we need to eval­u­ate it and bind the result to a symbol.

In order to gen­er­ate the out­put code (let [actual-value (inc 1)] …) which will eval­u­ate (inc 1) at run­time, we need to ref­er­ence the actual expres­sion in its quoted form, hence ~actual.

Note the dif­fer­ence in the expanded code if we don’t unquote actual.

image

with­out the ~, the gen­er­ated code would look for a local vari­able called actual which will fail because it doesn’t exist.

Out­putting the actual-value symbol

In order to out­put the actual-value sym­bol in the let bind­ing we had to write ~’actual-value, that is, (unquote (quote actual-value)).

image

I know, right!? Took me a while to get my head around it too.

Q. Can we not just write ‘(let [actual-value ~actual] …) ?

A. No, because it’ll trans­late to (let [user/actual-value (inc 1)]…) which is not a valid let binding.

Q. Ok, how about ~actual-value?

A. No, because the macro won’t com­pile as we’ll be look­ing for a non-existent local vari­able actual-value inside the scope of def­macro.

Q. Ok.. or ‘actual-value?

A. No, because it’ll trans­late to (let [(quote actual-value)  (inc 1)]…) which fails at run­time because that’s not a valid syn­tax for binding.

Q. So how does ~’actual-value  work exactly?

A.  The following:

  1. (quote actual-value) to cap­ture the sym­bol actual-value
  2. unquote the sym­bol so that it appears as it is in the out­put code

Out­putting the actual and expected expres­sions

Finally, when for­mu­lat­ing the error mes­sage, we also saw ‘~actual and ‘~expected.

Here are the expanded code with and with­out the quote.

image

See the difference?

With­out the quote, the gen­er­ated code will have eval­u­ated (inc 1) and printed FAIL in 2.

With the quote, it’d have printed FAIL in (inc 1) instead, which is what we want.

Rule of thumb

  • to cap­ture a sym­bol, use ~’symbol-name
  • to ref­er­ence an argu­ment to the macro and gen­er­ate code that will be eval­u­ated at run­time, use ~arg-name
  • to ref­er­ence an argu­ment to the macro and gen­er­ate code that quotes it at run­time, use ‘~arg-name

 

Finally, let’s test out our new macro.

image

Sweet! So that’s it?

Almost.

There’s a minor prob­lem with our macro here – it’s not safe from name col­li­sions on actual-value.

image

 

Ver­sion 4

If you see # at the end of a sym­bol then this is used to auto­mat­i­cally gen­er­ate a new sym­bol with a ran­dom name. This is use­ful in macros as it keeps the sym­bols declared in macros from leak­ing out.

So instead of using ~’actual-value in the let bind­ing we might do the fol­low­ing instead:

image

When expanded, you can see the let bind­ing is using a ran­domly gen­er­ated sym­bol actual-value__16087__auto__:

image

Not only is this ver­sion safer, it’s also more read­able with­out the mind-bending (unquote (quote actual-value)) business!

 

So there, a quick(-ish) intro­duc­tion to homoiconic­ity and Clo­jure macros. Macros are a pow­er­ful tool to have in one’s tool­box, and allows you to extend the lan­guage in a very nat­ural way as clojure.test does. I hope you find the idea inter­est­ing and I have done the topic jus­tice and explained it clearly enough.

Feel free to let me know in the com­ments if anything’s not clear.

 

Links

Rust – memory safety without garbage collector

rust_ownership_3

I’ve spent time with Rust at var­i­ous points in the past, and being a lan­guage in devel­op­ment it was no sur­prise that every time I looked there were break­ing changes and even the doc­u­men­ta­tions look very dif­fer­ent at every turn!

Fast for­ward to May 2015 and it has now hit the 1.0 mile­stone so things are sta­ble and it’s now a good time to start look­ing into the lan­guage in earnest.

The web site is look­ing good, and there is an inter­ac­tive play­ground where you can try it out with­out installing Rust. Doc­u­men­ta­tion is beefed up and read­ily acces­si­ble through the web site. I per­son­ally find the Rust by Exam­ples use­ful to quickly get started.

 

Own­er­ship

The big idea that came out of Rust was the notion of “bor­rowed point­ers” though the doc­u­men­ta­tions don’t refer to that par­tic­u­lar term any­more. Instead, they talk more broadly about an own­er­ship sys­tem and hav­ing “zero-cost abstractions”.

Zero-cost what?

The abstrac­tions we’re talk­ing here are much lower level than what I’m used to. Here, we’re talk­ing about point­ers, poly­mor­phic func­tions, traits, type infer­ence, etc.

Its pointer sys­tem for exam­ple, gives you mem­ory safety with­out need­ing a garbage col­lec­tor and Rust point­ers com­piles to stan­dard C point­ers with­out addi­tional tag­ging or run­time checks.

It guar­an­tees mem­ory safety for your appli­ca­tion through the own­er­ship sys­tem which we’ll be div­ing into shortly. All the analy­sis are per­formed at com­pile time, hence incur­ring “zero-cost” at runtime.

Basics

Let’s get a cou­ple of basics out of the way first.

image

Note that in Rust, println is imple­mented as a macro, hence the bang (!).

Own­er­ship

When you bind a vari­able to some­thing in Rust, the bind­ing claims own­er­ship of the thing it’s bound to. E.g.

image

When v goes out of scope at the end of foo(), Rust will reclaim the mem­ory allo­cated for the vec­tor. This hap­pens deter­min­is­ti­cally, at the end of the scope.

When you pass v to a func­tion or assign it to another bind­ing then you have effec­tively moved the own­er­ship of the vec­tor to the new bind­ing. If you try to use v again after this point then you’ll get a com­pile time error.

image

image

This ensures there’s only one active bind­ing to any heap allo­cated mem­ory at a time and elim­i­nates data race.

There is a ‘data race’ when two or more point­ers access the same mem­ory loca­tion at the same time, where at least one of them is writ­ing, and the oper­a­tions are not synchronized.

Copy trait

Prim­i­tive types such as i32 (i.e. int32) are stack allo­cated and exempt from this restric­tion. They’re passed by value, so a copy is made when you pass it to a func­tion or assign it to another binding.

image

The com­piler knows to make a copy of n because i32 imple­ments the Copy trait (a trait is the equiv­a­lent to an inter­face in .Net/Java).

You can extend this behav­iour to your own types by imple­ment­ing the Copy trait:

image

Don’t worry about the syn­tax for now, the point here is to illus­trate the dif­fer­ence in behav­iour when deal­ing with a type that imple­ments the Copy trait.

The gen­eral rule of thumb is : if your type can imple­ment the Copy trait then it should.

But cloning is expen­sive and not always pos­si­ble.

Bor­row­ing

In the ear­lier example:

image

  • own­er­ship of the vec­tor has been moved to the bind­ing v in the scope of take();
  • at the end of take() Rust will reclaim the mem­ory allo­cated for the vector;
  • but it can’t, because we tried to use v in the outer scope after­wards, hence the error.

What if, we bor­row the resource instead of mov­ing its ownership?

A real world anal­ogy would be if I bought a book from you then it’s mine to shred or burn after I’m done with it; but if I bor­rowed it from you then I have to make sure I return it to you in pris­tine conditions.

rust_ownership_4

rust_ownership_5

In Rust, we do this by pass­ing a ref­er­ence as argument.

image

Ref­er­ences are also immutable by default.

image

But just as you can cre­ate muta­ble bind­ings, you can cre­ate muta­ble ref­er­ences with &mut.

image

There are a cou­ple of rules for borrowing:

1. the borrower’s scope must not out­last the owner

2. you can have one of the fol­low­ing, but not both:

2.1. zero or more ref­er­ences to a resource; or

2.2. exactly one muta­ble reference

Rule 1 makes sense since the owner needs to clean up the resource when it goes out of scope.

For a data race to exist we need to have:

a. two or more point­ers to the same resource

b. at least one is writing

c. the oper­a­tions are not synchronized

Since the own­er­ship sys­tem aims to elim­i­nate data races at com­pile time, there’s no need for run­time syn­chro­niza­tion, so con­di­tion c always holds.

When you have only read­ers (immutable ref­er­ences) then you can have as many as you want (rule 2.1) since con­di­tion b does not hold.

If you have writ­ers then you need to ensure that con­di­tion a does not hold – i.e. there is only one muta­ble ref­er­ence (rule 2.2).

There­fore, rule 2 ensure data races can­not exist.

rust_ownership_2

Here are some issues that bor­row­ing pre­vents.

Beyond Own­er­ship

There are lots of other things to like about Rust, there’s immutabil­ity by default, pat­tern match­ing, macros, etc.

Pat­tern Matching

image

Structs

image

Enums

image

Even from these basic exam­ples, you can see the influ­ence of func­tional pro­gram­ming. Espe­cially with immutabil­ity by default, which bodes well with Rust’s goal of com­bin­ing safety with speed.

Rust also has a good con­cur­rency story too (pretty much manda­tory for any mod­ern lan­guage) which has been dis­cussed in detail in this post.

Over­all I enjoy cod­ing in Rust, and the own­er­ship sys­tem is pretty mind open­ing too. With both Go and Rust com­ing of age and tar­get­ing a sim­i­lar space around sys­tem pro­gram­ming, it’ll be very inter­est­ing to watch this space develop.

 

Links

Erlang on Xen

Stum­bled across this slid­edeck today, it’s very infor­ma­tive and so I feel obliged to share!

InfoQ interview at BuildStuff 14

The video and inter­ac­tive tran­script is also avail­able on InfoQ’s page here.