or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Syncing
xxxxxxxxxx
Design meeting meta notes
Goals from this meeting
Our goals for this meeting, in order of priority:
#t-lang
.Our proposal
We propose to create an RFC outlining the principles and giving details and examples of how they are to be used. We also propose to incorporate the design principles into our lang team design processes, and give one example of how that could be done.
Some key changes from prior efforts:
RFC sketch
This section contains a sketch of the RFC content we would expect to include, though it's not formulated in RFC form.
Goal/motivation
A set of high-level principles and priorities and tradeoffs that will…
These principles are focused on how the lang team approaches the design of the language itself. Although we expect significant overlap with the guiding principles from other teams, we would rather begin with the language and then consider whether to go for broader consensus.
Rust's goal
Rust's overall goal is to empower everyone to build reliable and efficient software.
The principles we use to achieve it
The following list of principles describe how Rust should feel; they are meant to complete the phrase "Rust empowers by being…".
How to use the principles:
Reliable: "if it compiles, it works"
Examples that embody reliability:
?
and#[must_use]
to highlight errors and make sure they are handledOption
orResult
instead of panickingPerformant: "idiomatic code runs efficiently"
Examples that embody performant:
Transparent: "you can predict and control low-level details"
Examples that embody transparent:
Question: We also use "transparent" to mean "versatile" in some places – basically, "whatever you want to do, you can do it with Rust". For example, requiring all values to be move is a blow for versatility. Are these distinct principles? Should we have both? Can we say Transparent and Versatile?
Supportive: "the language and tools are here to help"
Examples that embody supportive:
Compositional: "great things that work great together"
Note: We are not sure the best word here! Here are some alternatives we considered, but other suggestions are welcome: "Orthogonal", "Productive", "Interoperable"
Examples that embody compositional:``
.await
so that it combined with?
operator and chainingIterator
Opinionated: "easy things are easy"
Examples that embody opinionated:
T: Sized
by defaultResult
,?
,#[must_use]
, etcmain
to returnResult
How we propose to use the principles
- The image was uploaded to a note which you don't have access to
- The note which the image was originally uploaded to has been deleted
Learn More →Design principle showdown
When we encounter a sticky design question, we can go through the principles one by one and write down the ways that they are impacted by each of the options at play. To make this easier, we could create a template document that can be filled in, which could look something like the following. This document is designed so that all participants can agree to its contents, even if they don't agree on the outcome.
It may also be better to organize the analysis by option – which principles are strongly upheld? neutral? strongly worked against?
RFC template extension
Extend the RFC template with a design principle showdown section.
Case studies
This section presents three case studies, two where the princples are well-balanced and one where they are not.
Well balanced: Memory safety, out of bounds memory accesses
The classic Rust tradeoff is memory safety itself. It makes programs reliable, for obvious reasons, but it also helps to ensure they are performant by enabling the compiler to do more aggressive optimization (or it will, once we settle on the right rules).
However, there are limits to what Rust is able to prove statically. A pure focus on Reliability would lead to a system that incorporates a full theorem prover, thus eliminating all unsafe code. However, this approach would violate supportive and opinionated – the full generality of such a language would make it very hard for people to do things.
We mitigate this in two ways:
vec[i]
are checked at runtime. This favors reliability over performant. For most applications, the cost is minimal.unsafe
code, which enables raw pointers and unchecked vector accesses. This preserves transparency, but preserves (in the large) reliability, albeit making it the responsibility of the user.Well balanced: Undefined layouts and repr annotations
We choose to make struct layouts undefined unless users explicitly opt-in. Giving the compiler room to optimize layouts helps make programs more performant, but
#[repr]
annotations preserve transparency. The guideline that#[repr]
annotates should never change the semantics of a struct (though they can subset its behavior) preserves reliability.Well balanced: Error-handling design and "making things look like exceptions"
The
?
operator is a classic example of balancing transparent with supportive and opinionated. We want error handling to be easy and for there to be clear patterns (arguably we've got a ways to go here), but we also want people to avoid the exception failure mode that it's hard to predict what happens in the case of error. The?
operator is explicit and traceable, preserving transparency, but still short and unintrusive, minimizing the impact on opinionated. It is coupled with strong patterns (e.g., usingResult
for errors) that permit helpful compiler error messages, preserving supportiveness (and indeed the RFC spent some time talking about the best errors to give when users misuse?
).On its own, however,
?
andResult
violate compositionality. This is why we are (slowly) pursuing a generalization in the form of theTry
trait, so that the concept of unpacking a result into a "normal" and "abrupt" form can be generalized.(An argument for
try fn
, which we will not go into here, is a case that that we are insufficiently opinionated with?
, and too transparent, requiring too much generic boiler plate such asOk
-wrapping.)Poorly balanced: Allocation and copy vs clone (also an anti-pattern)
Current Rust rules draw a sharp distinction between
Copy
values, which can be implicitly copied just by copying bytes from one place to another, andClone
values, which must run custom logic. This rule was meant to ensure that Rust programs are transparent – given a function call likefoo(a)
, there is no implicit execution of arbitrary code to "clone"a
. This ensures (among other things) that allocations are marked with some kind of explicit syntax.However, this rule arguably makes Rust programs less reliable and performant:
Vec<u32>
, I get a fresh vector that is independent from the original. Vectors (and all deeply owned types) thus operate as values, same as au32
but with a more expensive clone.Arc<Mutex<Vec<u32>>>
, I get a second handle to the same underlying resource, and mutations on one handle are visible to the other.let y = x.clone()
, I cannot know the extent to whichx
andy
are "interlinked" without knowing the details of their type and implementation.Rc<[u32; 10*1024]>
requires writingx.clone()
but copying a slice not wrapped inRc
does not?Box
into an enum can make it radically smaller and make your code a lot faster. We don't do the "optimal thing" by default in cases like this, favoring transparency instead (of course, the "optimal thing" is very difficult or even impossible to determine in general).Poorly balanced: All types require move and all values are affine
At least with its builtin rules, Rust requires that all values (1) can be moved from place to place; (2) can be dropped; and (3) can be forgotten. In contrast, C++ does not work this way, but instead says that a value never moves, instead providing a way for a value to "take its value" from another value. This is an opinionated choice for Rust and it can be very helpful when writing code, however, it violates transparency, because there are low-level details that Rust doesn't let you control. This has led us to the idea of pinning, which remains an awkward and poorly integrated element of Rust.
Poorly balanced: Sync and async functions do not have the same capabilities
We've violated compositionality because sync functions have the ability to invoke a closure and then guarantee that they can take action after that code terminates, even in the event of panic or forgets, but async closures lack that capability (we don't guarantee poll-to-completion). This in turn hinders our ability to integrate rayon-style parallelism into async.
Unclear: auto traits
We decided early on to make
Send
andSync
into auto traits. This upholds performant, opinionated, and arguably composition by encouraging Rust users to adopt strict ownership patterns that, in turn, support parallelization. It trades off reliability by making semver decisions more complex; in principle this can be mitigated through supportive tooling, but the rust org has never invested in such tooling (there are third party options out there). It also trades off transparency, such as in the rules for how Send flows through function types without always being mentioned.Other examples
Here are some other interesting examples of specific tradeoffs we have encountered.
lock
on aMutex<T>
should be returned a guard and panicked in the case of poisoning). Others might argue that the cost to performant is too high and benefits to reliability are too low. This is an example that is in accordance with the ordering of the principles, but still feels "suboptimal" because its overall impact is too high.Boxing::new
(transparent) vs opinionated (see these blog posts)Rules of thumb
The following are examples of "rules of thumb" that we follow to help ensure that we are uploading the principles (and sometimes to help us maintain other constraints that may not be directly captured by the principles). They are not as general, but they're quite useful. We're writing them down to capture them as documentation and reference.
git diff
, in editors without IDE features configured, in emails/documents, and on web pages. We don't tend to incorporate language features that rely heavily on such features to write, read, or understand.Rough sketch of a rule of thumb, which Niko doesn't fully agree with yet:
Frequently asked questions
Decisions we made and the rationale behind them.
What about community-oriented principles?
This list focuses on Rust's language design; community principles or broader Rust project principles would be a much broader document, and we don't think that should be combined with these aspects of language design. Among other things, the language design principles are designed to explain tradeoffs we make, and community principles might have similar tradeoffs, but we don't want to approach potential tradeoffs between the two kinds of principles in quite the same way.
What about stability without stagnation?
There are other kinds of principles that Rust has evinced that have more to do with the way we operate. These are good to talk about, and interact with the principles, but they are not the same as.
Why is ergonomic not on the list?
Begs the question of ergonomic for what – opinionated (easy things easy) covers the case of making simple programming tasks straightforward, but for complex software meant to last a long time, ergonomics involves reliability, performant, etc.
Why is "familiar" (e.g. from other languages) not on the list?
Rust doesn't seem to place existing precedent that high on the list. We don't intentionally reinvent the wheel, but we are willing to do something different (e.g. Rust's syntax is C-inspired; we write
.await
and notawait x
).Questions for discussion in this meeting
Should we start with this as a lang document?
It's useful to focus on lang to keep things moving, but a lot of the principles (e.g., supportive, orthogonal) are more cross-cutting, and indicate areas where the language design works in concert with the stdlib and tooling to achieve the desired effect. Maybe it would be better to frame this as a whole project effort from the start?
An interesting question to consider is how we would go about modifying the principles – suppose that we adopted them and then found that something didn't seem right, or there was a missing principle. What would we do and how would it play out differently if this were a lang-team vs whole-project document?
What kind of language would we get if we reordered the principles?
Worth checking our assumptions, and exploring what kinds of language design we would get by having different priorities.
What other exercises could we do to validate the principles?
How can we validate the existing set of principles and check for exhaustiveness? Possible examples:
Other ideas? Do those things seem worth doing?
Do we have the right model? e.g., Ordering vs "Goldilocks"?
The principles are based on the idea of ordered tenets, as practiced at Amazon and other companies. It's possible that a "total ordering" isn't the right model. Another interesting one is the "goldilocks" model, where for each principle we list what it means to have "too little", "just right", and "too much". This seems like a useful exercise regardless. To avoid wasting meeting time, let's avoid discussing this unless anyone wants to actively argue AGAINST ordered tenets. In other words, adding goldilocks-style analysis seems like a "pure win", but it also doesn't contradict ordering, which also adds value and is easy to understand.
Here are some examples of going "too far". In these cases, what makes each of them "too much" is that they go beyond "trading off" another principle to "completely disregarding it":
This is an interesting example where it's not clear if the problem is that it violates some other principle. This may indicate a missing principle (long-term maintenance):
Is the set of principles correct? Anything missing?
Some interesting points to discuss:
#[non_exhaustive]
attribute).How else can we integrate the principles into our lang process?
We make two suggestions, tradeoff documents and modifying the RFC template. Are there other ideas?
Meeting discussion
Add topics here, following the template below! We've left some placeholders for you. Before using the last one, add some more so people can easily edit in parallel.
Meeting etiquette: Please avoid typing answers to people's questions, unless you are simply clarifying a point of confusion, or asking them to clarify their question. Keep substantive discussion for the meeting so that everyone can participate. We add notes during the meeting with the major and comments raised.
How do I add a question?
Ferris: Use this format! Make a new
##
section, summarize your question, and put your name at the front.Consensus?
Niko: Do we have consensus to do something like this?
scottmcm: Would love to have something like this.
One way I've phrased this in the past is
Similarly, having these to help people make arguments more productively on RFCs would be a big help. I liked the note below that it would be possible to write a doc that everyone could agree reflected how the feature impacts the principles, even if those people potentially disagree on whether those impacts are acceptable or not.
"straightforward"?
scottmcm: the translation to machine code is described as "straightforward", but we have a bunch of things where the optimized machine code translation is anything but, like in optimized iterator loops. Is that ok? Could we find another word that fits better?
scottmcm: (this is slightly weeds, so could be moved down)
"weirdness budget" / "innovation tokens"
Yosh: I've never really loved this framing because it feels like it is only ever employed as a negative to state why something should not happen - but never as a reason for why something ought to happen. E.g. I've never heard: "Hey we have budgeted for weirdness on this feature, we get to be weirder about it if we want to.". The "budget" or "tokens" are always something we are short of, running out of, and that's stated as a reason to not do things. We don't really know how many tokens we have, just that we don't have enough of them.
Yosh: In a way I feel like the concept of "innovation tokens" or "weirdness budget" describe an inherent conservative force which does not directly translate to actionable properties. The least weird way of doing things will always be to preserve the status quo. The smallest change is always to keep things as they are. We know this is the case, and it will always be brought up as a concern. Because of that I don't think this should be elevated to the status of "value". Instead it seems more useful to discuss proposals based on whether we have a good story to teach them, whether they're internally consistent with the language, whether they're inherent / orthogonal, etc.
Josh: Does the emphasis on "innovation tokens" as the primary phrasing help there?
Yosh: Not really, it's inherent to the premise of it I think. Though it does give it a less negative spin.
Yosh: That said, I do think there is something there that we need to do justice to exploring the obvious, conventional solution. This feels maybe like: "make sure to test the null hypothesis" or "if you do something different, it needs to be justified". Not quite about budget and stuff we run out of - but more about due diligence, teaching, etc? Are there different values we could be swapping in here to capture the same essence of what this tries to get to?
scottmcm: Office used to have a saying of "all change is bad unless it's great" (or something like that), which I think is aiming at the same sort of thing.
scottmcm: Personally I'm not a fan of it on its own, as "weirdness" is very history-dependent. Braces are either entirely normal or very weird, depending on background.
Defaults are the reasonable ones, not necessarily the fast ones
scottmcm: One thing we're usually good at is making the "default" version the one that behaves reasonably, and having
foo_somesuffix
be the version that trades off understandability for (potential) speed. Should I think of that as "reliable" or "opinionated"? Those choices have different relative orderings to "performant", which is why I'm trying to figure out how I should think about it.Case Study: Type Inference
scottmcm: I think type inference is mostly considered a good choice nowadays – though https://graydon2.dreamwidth.org/307291.html recently suggested Graydon would have preferred something else – but if I look through these principles I have a hard time justifying that. It's clearly good for opinionated, but that's the last thing, and it's not necessarily good for anything else, and many consider it worse for that. Is there something missing that would cover it? At least outside of some of the worst stability gotchas we have with it, like people using
x.as_ref()
when they should use&x
.(Question inspired by the "inferred enums" threads that keep popping up on IRLO, for something like
send_request(.Get, url)
as opposed to needinguse crate::foo::HttpMethod; send_request(HttpMethod::Get, url)
.)nikomatsakis: lol
Transparent examples
pnkfelix: The transparency examples in the doc strike me as 1. potentially niche (not used by a massive percentage of Rust audience) and 2. not illustrative of how translation in Rust strives to be transparent (in the sense that the translations are straight-forward and predictable). However, reading further into the doc, I thought the example of
?
is a good example of transparency, in the sense that it is easy to understand the effect of<expr>?
on the code, via a local transformation. (Locality is not a necessary precondition for transparent translation, but it certainly helps.)Great principles say what they're giving up
TC: Principles that sound "too positive" don't always provide great guidance. Think of a company principle like "we're innovative". Who doesn't want to be that? But it doesn't guide behavior as much as a principle like "move fast and break things" (setting aside whatever else you may think about that). Move fast and break things guides decisions. That principle will be used in the organization to make decisions in a way that an unbalanced principle will not.
Niko: This is expressed in the tension between the principles, rather than directly in the principles themselves.
"Transparent" and "controlling low-level details" are separate
TC: There's a meaningful different between a language being transparent and giving control of low-level details. For example,
async
has a lot of magic, and we've recently discussed other features that may have low-level compiler magic, but they're in the service of giving fine-grained control."Expressiveness" may be a good principle
TC: Expressiveness may better capture the idea of control of low-level details. A maximally expressive language is one where the programmer can get the exact runtime behavior and memory layout wanted. In Rust, that tends to mean that we want as much of that to be possible in safe code as possible. Expressiveness also means that you can build higher-level abstractions that still achieve your low-level vision of the behavior.
The trap of truisms
One thing to watch out for is that we don't end up saying: "we want good things because they're good" or similar. I think this may be the same point TC mentions above about "great principles are about what they're giving up" - but from the opposite angle?
Rules of thumb should, if possible, be children of one or more principles
pnkfelix: reading the "local reasoning" rule of thumb, I said to myself "this sounds like a special case of why transparency is important." (But perhaps that claim itself is not well founded…) If my claim is right, then that leads me to wonder: do all of the rules of thumb actually have grounding in principles (and perhaps we have not established some missing principle that would be the appropriate parent for a rule of thumb).
Missing principle: Stable
TC: We probably think of this as going without saying, but we should comment on how we think about stability as a principle. It's a cultural virtue that everyone in this room shares, and it captures a philosophical notion and that is broader than a particular set of rules about what we can or cannot break. In the Linux kernel, for example, it's famously one of Linus' guiding principles: "don't break userspace!"
The relationship of lang values and the "drawbacks" section in the RFC template
Yosh: Writing it here because we closed the topic in the discussion. But the RFC template currently has a drawbacks section which is left suspiciously open? Could that potentially be a place where we instead ask people not to list drawbacks, but tradeoffs. What is gained? What is lost? Which values does this value over others?