
Rust's language ergonomics initiative - aturon
https://blog.rust-lang.org/2017/03/02/lang-ergonomics.html
======
modeless
Are there plans to do user studies? 10 minutes watching new users code in Rust
will give you better ideas than 10 weeks thinking about the problem in your
head.

I feel like there's a real lack of user testing in software development tools
land. If you're developing software for unsophisticated users it's obvious
that you should be doing user testing, but it's an often ignored fact that
developers are users too! APIs, compilers, build tools, they could all be
vastly improved with some user testing.

~~~
hackuser
> 10 minutes watching new users code in Rust will give you better ideas than
> 10 weeks thinking about the problem in your head.

I'd add: Do you want to focus on new or experienced users? For example, when
implementing systems that will be used 8 hrs/day by its users, we look only at
efficiency for experienced users (unless the political situation requires
placating the noobs). They will be noobs for a day or maybe a couple weeks;
they'll be experienced all day, every day for years. We explain it that way at
the beginning and they thank us later.

An example many are familiar with is Vim: Steep learning curve, but I'm so
happy that the focus is on efficiency for veteran users.

~~~
nathancahill
What's the language that takes that to the extreme? I remember it being used
for fintech(?) or spreadsheets and I remember seeing one-liners that look like
someone just mashed the keyboard. Apparently incredibly efficient once you are
an expert in the language.

~~~
throwaway7645
The APL family of languages that currently have commercial implementations
like Dyalog and Q w/ kdb+ database. J and GNU APL are both free & open source.
Dyalog is nice though even though a commercial license sets you back a grand
per year (basically what most C# users pay for VS). You get a nice
interpreter, support, built-in graphics, full .NET interop, full R interop,
heck even DDE which is still useful in finance and my own industry. They seem
to take documentation really seriously and have pretty interesting
conferences. I don't code in APL, but am considering it as their product just
seems pretty agile for my needs (personal user productivity). Q is extremely
expensive and is the APL derived language that accompanies the kdb+ database
used for time-series analysis on stock quotes...etc. It was built by Arthur
Whitney and legend has it the entire source code is 5 pages of C (being an APL
guy he has it all scrunched up though...doesn't like to scroll). People seem
to like it and be willing to pay a fortune.

------
yarper
After writing Rust in production for a while, the biggest bugbear I have is
the naming/file structure.

I end up a lot with this;

    
    
        src/main.rs
        src/combobulator/mod.rs
        src/combobulator/tests.rs
        src/tests.rs
        src/somethingelse/tests.rs
        src/somethingelse/mod.rs
    

Because I find tests in the same file a bit confusing. It's really easy with
maven-style layouts to know that "only things in main/java or main/scala get
compiled and go into the jar". "src/test/*" and "src/main/resources" are for
me. The same thing applies for cargo.tomls and resources - there's not really
a way to see what goes into the executable from the file structure.

But this isn't the biggest problem with having things called "mod.rs". That
would be if I open 5 mod.rs's in a text editor with tabs, I have no idea what
goes with what.

I know that tests should go under tests/, but that's specifically for
integration tests. Integration tests are an order of magnitude less likely to
get written imo, and if they are they'll probably get written as unit tests
anyway.

If anyone has any top tips for how to structure larger Rust projects while
separating unit tests into different files, please let me know!

~~~
JoshTriplett
I prefer to keep tests in a `mod tests { ... }` block at the end of the source
file, which provides comparable benefits and separation. I also prefer to use
`combobulator.rs` rather than `combobulator/mod.rs`, for multiple reasons
including filename ambiguity. In this case, you'd have:

    
    
        src/main.rs
        src/combobulator.rs
        src/somethingelse.rs

~~~
jnordwick
Too much code in one place. Makes it hard to read the file and difficult to
see what is code and what are tests. `wc` can no longer give you a quick
approximation of code size either.

~~~
erickt
In case you didn't see, I mentioned there is a way to do this right now:

[https://news.ycombinator.com/item?id=13791945](https://news.ycombinator.com/item?id=13791945)

~~~
jnordwick
So I can keep a separate src/ and tests/ hierarchy? Why are the rust people so
insistent on this even though most seen to want to could their test and src
trees.

------
ww520
Speaking of removing friction, there are three areas that have caused me grief
when I wrote Rust code:

1\. Error handling. The lack of built-in support for multi-error or error
union in Result is painful in dealing with different types of error in a
function. Support for Result<Value, Error1 | Error2 | Error3> would be
helpful. Or may be support for easily converting one type of error to another.
Now there's lots of boiler plate code to deal with error conversion. Error
chaining would be nice, too.

2\. Lack of stack trace when an error occurs. Now that stacktrace starts when
panic!() is called, which is kind of late.

3\. Better support for conversion between &str and String. Dealing with
strings is so prevalent in programming that making it easier to work with the
two types would be a huge boost to productivity.

Edit: another item

4\. Support of partially applied function , i.e. bind a subset of arguments to
the function pointer. Currently there's no way to bind the self argument to
the Option/Result chaining calls. Basically the Option/Result chain
(.and_then, .map, etc) only carries forward the value of Option/Result and
nothing else. It would be nice put partially applied function in the chain.
e.g. result.and_then(self.func1) where func1 has the self argument bounded. Or
in more general form, result.and_then(func1("param1", param2, _)) where
func1's first and second parameters have been bounded up front and the value
of result will be passed in as the 3rd parameter.

~~~
JoshTriplett
> 1\. Error handling. The lack of built-in support for multi-error or error
> union in Result is painful in dealing with different types of error in a
> function. Support for Result<Value, Error1 | Error2 | Error3> would be
> helpful. Or may be support for easily converting one type of error to
> another. Now there's lots of boiler plate code to deal with error
> conversion. Error chaining would be nice, too.

There are a couple of crates that support this; personally, I recommend the
"error-chain" crate. However, I _do_ wish that Rust promoted the most capable
of those to the standard library.

> 2\. Lack of stack trace when an error occurs. Now that stacktrace starts
> when panic!() is called, which is kind of late.

error-chain provides that.

> 3\. Better support for conversion between &str and String. Dealing with
> strings is so prevalent in programming that making it easier to work with
> the two types would be a huge boost to productivity.

Can you give some specific examples of cases you've found cumbersome?

String has a Deref instance for &str, so taking a reference to a String
automatically works as a &str. You can also call .as_str().

Going in the other direction, you can call .to_string() to make a copy of a
&str as a new String.

~~~
rspeer
> String has a Deref instance for &str, so taking a reference to a String
> automatically works as a &str. You can also call .as_str(). > > Going in the
> other direction, you can call .to_string() to make a copy of a &str as a new
> String.

As a beginner in Rust myself, I would like to observe that &str vs. String
problems come up all the time for me. Every one of them is quickly resolved by
knowing where to add an & or a method call, it seems, and experts know when to
do this, and they stop noticing the problem because it's only the beginners
who are doing it wrong.

But when every beginner is doing it wrong and every expert isn't, there is an
ergonomics problem.

~~~
mathw
I think part of the problem might be that String and &str are not related in
any intuitive way. My initial work in Rust was littered with what the heck is
this, what's a str that there's a reference to, etc.

I understand now, but initially it made absolutely no sense that String is a
heap-allocated owned string and &str is a reference to a chunk of string data
of known length stored somewhere that I probably don't have to care about.

It may be a teaching thing, but I do wonder if they could have been named
better. I just don't really know what else you'd call them without them
becoming overly verbose.

~~~
steveklabnik
I often joke that String -> StrBuf is my #1 wishlist item for a theoretical
Rust 2.0.

------
killercup
I especially like this approach:

> Often, the heart of the matter is the question of what to make implicit. In
> the rest of this post, I’ll present a basic framework for thinking about
> this question, and then apply that framework to three areas of Rust […]

What's proposed here is a universally good way to think about what to make
implicit. The proposed changes to Rust are just some applications of this.

~~~
zzzcpan
"What's proposed here is a universally good way to think about what to make
implicit."

I had a completely opposite reaction. It ignores all of the important things
that make usability good and instead focuses on the approach that essentially
promotes inconsistencies in design.

"The basic thesis of this post is that implicit features should balance these
three dimensions. If a feature is large in one of the dimensions, it’s best to
strongly limit it in the other two."

~~~
kbenson
Based on the actual examples, I'm not sure it _promotes_ inconsistency in
design, as long as it's not the sole deciding criteria. As a tool to do first
pass exclusion of ideas I think it has a lot of promise.

The major problem I see is that it's fairly subjective at the moment in what
you consider when thinking about those criteria. For example, in the section
about eliminating the need for mod (which was admittedly presented as
radical), the following was stated: _You could instead imagine the filesystem
hierarchy directly informing the module system hierarchy. The concerns about
limited context and applicability work out pretty much the same way as with
Cargo.toml, and the learnability and ergonomic gains are significant._ I think
this is a case where the learnability would suffer quite a bit. If I
understand it correctly, this changes the filesystem from a unidirectional
resource to a bidirectional one, where the presence of arbitrary files not
specified (as opposed to a well understood singular file, such as Cargo.toml)
might change how the code is interpreted.

~~~
oconnor663
I think the idea is that `foo::something()` without a module declaration
either implies the existence of `foo.rs`, or fails to compile. I'd agree that
it would be bad if creating `foo.rs` changed the behavior of code that was
previously doing something else (other than breaking the build).

What would happen, I think, is that adding a `mod foo { ... }` definition
would change the behavior of code that previously implicitly referenced a
`foo.rs` file. But that seems less crazy to me, since you've got a change in
one file affecting something else in that same file. Or it might make sense
for that to be a "conflicting module definitions" error.

------
twic
I'm really encouraged by this post. I ran into a situation somewhat related to
the borrowing in match patterns this week [1], and whilst it's only a mild
annoyance, it's lovely that it might get smoothened out. Today, i started
using modules in anger, and was immediately mildly annoyed by the need to
explicitly reference crates in my code, when they're already in my Cargo.toml,
and to declare modules, when they're implied by my file structure, so i'm
happy to see that that is on the radar too!

The file structure one makes me laugh, because one language that does
implicitly create modules from file structure, in exactly the way Rust would
need to, is Python, which is the one with the whole "explicit is better than
implicit" deal!

[1]
[https://www.reddit.com/r/rust/comments/5whke7/deref_coercion...](https://www.reddit.com/r/rust/comments/5whke7/deref_coercion_in_an_if_let/)

~~~
EugeneOZ
please fork the language and call it somehow RustyRuby if you prefer things to
be implicit.

------
CalChris
As I've said/posted this elsewhere, the Rust macro package is close to
unusable. It makes easy stuff difficult and it doesn't exactly help with
difficult stuff.

It would be interesting to compare the number of macros defined in the crates
corpus divided by total line count and compare that with other languages. I do
not think that I am alone in not using it. Yes, I use macros; I just don't
program macros.

Obviously, Java has shown that you can survive without a macro pre-processor.
That was even a point Gosling+Co made in a white paper I read way back in the
day. But I do believe that if you are going to have a macro processor, it
should be an expedient. Rust's macro processor is not expedient. It is its own
impediment.

I'm used to using macros. I use them in C and I use them in assembly. These
are both low level languages which Rust claims to be. Not being able to use
Rust's macros in the style to which I've become accustomed is infuriating.

~~~
steveklabnik
Macros are being largely re-done, see [http://words.steveklabnik.com/an-
overview-of-macros-in-rust](http://words.steveklabnik.com/an-overview-of-
macros-in-rust) for an overview.

Honestly, I very rarely use macros and have written two in my years of Rust.
You almost never need them, or at least, that's my experience.

~~~
CalChris

      #define M_PI        3.14159265358979323846264338327950288

~~~
pklausler
3.141592653589793115997963468544185161590576171875 is the exact decimal
representation for the 64-bit IEEE-754 number that's closest to pi (viz.
0x400921FB54442D18).

Any time you see a decimal floating-point constant with a nonzero fractional
part that doesn't end in '5', you're looking at a bug.

EDIT: As long as this grizzled old Fortran programmer is giving out free
advice, I'll add two more items every programmer should know about binary
floating-point:

a) Every binary floating-point number can be represented exactly in decimal
notation if you use enough digits.

b) Those decimal values are the only ones that can be exactly converted to
binary; all of the rest require rounding.

~~~
kazinator
> _Any time you see a decimal floating-point constant with a nonzero
> fractional part that doesn 't end in '5', you're looking at a bug._

That's just silly. If you're writing some famous mathematical constant, the
digits should match that constant, and not the requirements of the machine.
(Except for the last one being rounded off.) Suppose we had a floating-point
machine that gave us maximum 4 digits of decimal precision. I wouldn't define
the PI constant as 3.145. That would just look like a typo to people who have
PI memorized to half a dozen digits or more. I'd make it 3.14159 (or more) and
let the darn compiler find the nearest approximation on the floating-point
axis.

~~~
pklausler
Any exact decimal representation of a specific binary floating-point number
that's finite and not an integer must end in the digit '5' (perhaps with
trailing zeroes). This is because its fractional part is (the sum of) a set of
powers of two with negative exponents, and their exact decimal representations
(0.5, 0.25, 0.125, &c.) all end in '5' (proof by induction is obvious and left
to the reader).

------
spraak
> Right now, such a signature would be accepted, but if you tried to use any
> of map’s methods, you’d get an error that K needs to be Hash and Eq, and
> have to go back and add those bounds. That’s an example of the compiler
> being pedantic in a way that can interrupt your flow, and doesn’t really add
> anything; the fact that we’re using K as a hashmap key essentially forces
> some additional assumptions about the type. But the compiler is making us
> spell out those assumptions explicitly in the signature.

I feel this exact same way with Go. E.g.

    
    
        x := map[string]map[string]int{
            "key": map[string]int{
                "another": 10,
            },
        }
    

Given that the outer type signature says that the `value` of the map should be
a `map[string]int` it's sometimes quite annoying to specify that inner type
over again

~~~
zeeboo
You can leave the inner type out...

[https://play.golang.org/p/m3bLmneArB](https://play.golang.org/p/m3bLmneArB)

~~~
spraak
Ah, maybe it's when they're alias types that that wouldn't work?

~~~
HeyImAlex
Actually I just tried that and it worked also. But I think you're right that
this hasn't always been the case because I remember being annoyed about it. Or
maybe we're both crazy.

You definitely need to specify the type for an interface, but that makes
sense.

~~~
unscaled
You're not crazy. I also have vague memories of maps being more obnoxious in
Go. :)

The facts are that map literals did change a bit in Go 1.5 (i.e. fairly
recently), full literal specification for values could already be elided back
then - it was KEYS that required full specification:
[https://golang.org/doc/go1.5#map_literals](https://golang.org/doc/go1.5#map_literals)

If you go way more back, Go 1 also saved some typing on values, in case they
were pointers:
[https://golang.org/doc/go1#literals](https://golang.org/doc/go1#literals)

------
swuecho
Great！

but do not forget to document what is implicit. Otherwise, it is magic and
make it more confusing. That is the impression of my last attempt to learn
rust.

------
the_mitsuhiko
My biggest and probably only real frustrating with Rust is that modules and
crates live in the same namespace. That makes stuff incredibly confusing to
teach and read. I can otherwise live with the explicit extern/mod if needed.

~~~
lohengramm
I also had problems understanding this whole crate system when I was playing
with Rust.

~~~
steveklabnik
This is extremely common, and is one of the reasons why it's such a big part
of this post.

~~~
EugeneOZ
but in the post reason of the pain (conventions) described as a cure, and it's
really awful.

------
zengid
A bit of inspiration can be gleaned from the work of Dr Stefik on Evidence-
based language design [1][2].

[1]
[https://www.youtube.com/watch?v=uEFrE6cgVNY](https://www.youtube.com/watch?v=uEFrE6cgVNY)
[2]
[http://dl.acm.org/citation.cfm?id=2534973](http://dl.acm.org/citation.cfm?id=2534973)

~~~
steveklabnik
Thank you for this link! I'd been trying to find this again...

------
Tarean
The implied bound one reminded me of a very similar thing in haskell
[https://prime.haskell.org/wiki/NoDatatypeContexts](https://prime.haskell.org/wiki/NoDatatypeContexts)
. Basically

    
    
        data Hashable a => Set a = ...
    

is completely useless. It only forces you to add constraints to functions
that, if necessary, would be required anyway.

Not to be confused with existential quantification

    
    
        data ExistentialSet a = forall a . Hashable a => ...
    

which carries a reference to the hash function in the instances, similar to
trait objects in rust.

~~~
tatterdemalion
DatatypeContexts turns on a feature that Rust already has - constraints on
type parameters to datatypes. This is about propagating constraints from the
datatypes to functions over it.

Haskell hasn't found DatatypeContexts very useful, but Rust has. In my opinion
this is largely because of the difference in what our type systems mean - in
Rust, types carry a lot more information about the memory model & data layout
than in Haskell. This has led to a different skew.

In Haskell, DatatypeContexts also create struggles with higher kinded
polymorphism (you can't implement Functor for the definition of Set you just
provided, for example), but in Rust, the same memory model concerns that make
datatype constraints useful make traits like Functor less useful.

------
raverbashing
Great initiative

By its very nature, Rust is harder (than let's say Python or JS). It is
compiled, there's not much runtime magic to rely on and low level is hard.

But thinking about this and trying to make it easier is important

~~~
the_common_man
What does hard have to do with compiled or not? Visual Basic was actually
compiled and it was and still one of the easiest languages to learn ever.

~~~
raverbashing
VB compiled to a VM that was embedded in the .exe file

~~~
pjmlp
PCode was only up to version 6.

Version 6 introduced an actual AOT compiler to native code.

Also its older brother, QuickBasic, compiled to native code.

Incidently VB is now again being AOT compiled to native code, via .NET Native
and CoreRT.

------
hinkley
I'm an ergonomics nut and I've been looking to learn Rust. Any chance they're
looking for a set of guinea pigs to report their experiences as new users, or
are they mostly working on already-known issues?

~~~
steveklabnik
We are always looking for experience reports. Making a post on internals.rust-
lang.org would be great!

------
raz32dust
I hope the ergonomics initiative takes it towards Java rather than Python. For
all the hate it gets, Java's explicitness is a boon when maintaining large
scale systems and preventing bugs.

~~~
actuallyalys
What parts of Java are you thinking of? When I think of explicitness Java has
but Python doesn't, types mostly come to mind, but Rust is already statically
typed, so that can't be what you mean.

------
trento
I was able to work with some rust developers at Hack Illinois recently. We
started the 2017 Rust cookbook [0] with Brian Anderson.

[0]: [https://github.com/brson/rust-cookbook](https://github.com/brson/rust-
cookbook)

~~~
killercup
Very cool! Took me a while to find the rendered version, though:
[https://brson.github.io/rust-cookbook/](https://brson.github.io/rust-
cookbook/)

------
MrF3ynmann
I still can't get my head around rust. While all those features definitely
make sense, I find it very confusing sometimes.

Is there something like rust for c++ programmers?

~~~
vatotemking
Curious, is it harder for c++ programmers to learn Rust than dev from higher
level devs? Because ive seen Rust has a lot more success recruiting devs from
python, js, even php.

~~~
pjmlp
Not really, it is mostly a culture thing.

There are two main communities in C++, those that embrace safety and take
advantage of the language features to improve their productivity, while going
down to lower level constructs if performance needs an extra push.

Then there are those that are kind of exiled C developers using a C++
compiler, forced to migrate to C++ on their work, trying to use it as C with
C++ compiler.

This is the group that has more issues with Rust.

~~~
vatotemking
That maybe it. Perhaps the first camp is more comfortable and familiar with
the "ocaml'ness" of Rust, while for the second (C devs) camp the concepts are
alien.

------
robohamburger
"Idea: implied bounds" sounds like a very interesting idea. It is a pain
copying the bounds as author mentions. I also have worked with library code
that does not consistently use trait bounds and it can lead to very confusing
errors.

The thing that keeps getting me now is there are so many types moving around
with generics and traits. It would be nice if it were easier for something to
be object safe and/or Any was more powerful. My solution as with many things
is to route around it but it is frustrating at times.

~~~
steveklabnik
> Any was more powerful

[https://github.com/rust-lang/rfcs/pull/1849](https://github.com/rust-
lang/rfcs/pull/1849) is relevant to your interests; however, as the comments
say there, associated type constructors would be needed before it could
possibly be used with Any.

~~~
robohamburger
Yeah it seems like you would need some kind of guard to make sure you don't
violate any of the lifetimes that could be tucked away. Still good work!

I guess compiler plugins and libraries will eventually fill in the gaps, until
then I guess I just have to be creative :)

------
k__
Python and Nim are good examples to learn from ;)

------
antiquark
They should take on a huge project, like say, converting the linux source code
into Rust.

The sheer bulk of the code will effectively "force" them to make Rust
ergonomic. They might even end up with annoying things, like different sized
ints on different CPUs, or... (horror of horrors)... running Rust through a
pre-processor as part of its compilation.

~~~
tedmielczarek
The Rust compiler is written in Rust, so rest assured the Rust core developers
spend a lot of time dealing with the language itself. Additionally Mozilla is
spending quite a bit of developer time on Servo, so we have quite a few people
actively writing a lot of Rust code.

~~~
kentrado
Only 7 or 9 actual developers that work for Mozilla in servo. IIRC.

------
StephenAshmore
I've been writing Rust quite frequently recently, and have enjoyed it.
However, the biggest problem I've faced so far is exactly what they are trying
to address. Modules can be annoying, and I wouldn't mind better optional
parameters or a better way to box up trait objects.

------
no_protocol
I am not sure if this directly applies to your post, but it came to mind when
I read the section about `extern crate`. It seems like the Cargo system is
relied upon by almost all Rust users, and I am not sure if the following is an
ergonomics problem or a lack of understanding on my part.

A couple months ago I was exploring Rust's web server capabilities after
seeing the "Are we _web_ yet?" page. I decided to try out the `iron`
package.[0]

I was quickly able to serve some basic content, but I wanted to add some
headers to the response.

I was able to `use iron::headers::Allow` to add an Allow header.[1]

Next, I wanted a Link header. Link wasn't available but I could get around
that by defining a custom header with the `header!` macro. Unfortunately, I
couldn't figure out how to get the `header!` macro for custom headers without
`#[macro_use] extern crate hyper` and adding `hyper` to the Cargo.toml
file.[2]

Then I wanted a `Vary` header. I was able to get that in with `use
iron::headers::Vary`, but I couldn't actually create one yet! In order to
create my `Vary::Items` header, I needed to also `use unicase::UniCase` and
add `unicase` to my Cargo.toml.[3][4]

So within an hour or so of starting the project, my explicitly listed
dependencies had grown from just `iron` to include two additional
dependencies. The iron package already relies upon hyper which already relies
upon unicase.

Here are some questions I am still left with. Would love any responses.

Is it possible for me to use the pieces described above without explicitly
listing these crates? If not, why do I need to declare hyper as a dependency
when iron is already using it? Perhaps I don't need to, and I was just unable
to figure out how to get the `header!` macro from iron directly. My initial
expectation was that iron would either wrap or expose every part of hyper that
I might need. The same goes for hyper not allowing me to just use the same
unicase it relies upon.

How am I supposed to get the correct version of hyper and unicase to match
with the ones that my version of iron was sent with? Do I have to go look them
up? Can use the latest version of `hyper` even if `iron` is a few versions
behind? What version should I be specifying?

[0]: [http://ironframework.io/doc/iron/](http://ironframework.io/doc/iron/)

[1]:
[http://ironframework.io/doc/iron/headers/struct.Allow.html](http://ironframework.io/doc/iron/headers/struct.Allow.html)

[2]:
[https://hyper.rs/hyper/v0.9.9/hyper/macro.header!.html](https://hyper.rs/hyper/v0.9.9/hyper/macro.header!.html)

[3]:
[http://ironframework.io/doc/iron/headers/enum.Vary.html](http://ironframework.io/doc/iron/headers/enum.Vary.html)

[4]:
[http://ironframework.io/doc/unicase/struct.UniCase.html](http://ironframework.io/doc/unicase/struct.UniCase.html)

~~~
dogamak
Macros and the importing and scoping of them is a mess in rust right now and
major rewrite of the macro system is underway. Currently crates have no way to
re-export macros from other crates and #[use_macros] brings all macros in the
imported crate to the local crate's namespace as-is (no way to prevent
possible naming conflicts)

~~~
Animats
_major rewrite of the ... is underway._

That's a common response to criticisms of Rust. At this late date, that's a
problem.

~~~
Manishearth
There are only two cases where that's been a thing. Macros, and nonlexical
lifetimes.

The major rewrite for macros has already brought fruit with Macros 1.1,
solving some of the more pressing issues.

The other one, nonlexical lifetimes, needed a complete overhaul of compiler
internals. The main part of that work happened with MIR, but there's still
work to be done. It's ongoing. This was work that was going to take a long
time, and it did.

------
DiegoRamirez
development "ergonomics is something close to my heart.

I'm a statically-typed, "easy-on-the-eyes" Python looking-loving guy, but
there's something beyond that, when it comes to "ergonomics".

I'm really impressed with the Clojure community and parinfer, paredit(old lisp
school) and just slinging code around with rapid feedback.

------
DiegoRamirez
I forgot, what's Rust's opinion on OO? I would hope it's non-traditional. We
need to get away from tradition OO and concentrate on what really matters -
dispatch!.

~~~
Animats
Rust calls them "structs". They're like classes, but different. Rust also has
"traits". They're like abstract classes, but different.

~~~
k__
Structs are for data and traits are for methods?

~~~
ww520
Struct is for data and Struct's implementation is for methods. Trait is like
interface, just a set of function signatures.

~~~
k__
Why isn't it called interface then?

~~~
paavohtl
Because they are traits [1], not interfaces, and they also have some features
from type classes [2].

When you create a class in an OOP language, you have do declare all interfaces
it supports, and provide implementations for them (or mark the class as
abstract). In Rust's case, you declare the data separately as a struct
declaration, and then implement the traits you want in impl declarations. You
can also implement traits for other traits, or for types from other modules,
including the standard library.

Finally, compared to traditional OOP interfaces Rust's traits can require
static functions in addition to instance methods and they can also implement
methods with an overridable default implementation.

[1]
[https://en.wikipedia.org/wiki/Trait_(computer_programming)](https://en.wikipedia.org/wiki/Trait_\(computer_programming\))
[2]
[https://en.wikipedia.org/wiki/Type_class](https://en.wikipedia.org/wiki/Type_class)

~~~
k__
Ah, I read the Wiki article and my impression of traits was "interfaces with
implementation" and as far as I could tell, Rust traits have no
implementation, they 'need' impl(emendations) so they seemed more like
interferences to me :)

~~~
steveklabnik
You can have default implementations for methods if you'd like.

------
pjungwir
A challenge I've had with Rust lately is factoring initialization code into
separate functions. Because of stack-based allocation it has to stay in the
main function. For example:

    
    
        pub fn do_many(iter: &mut Iterator<Item=String>) {
          let mut job_id = None;
          let job_id_env = env::var("MYAPP_JOB_ID");
          let mut log = if let Ok(val) = job_id_env {
            write_pid_file(&val);
            job_id = Some(val.clone());
            let home = env::var("HOME").expect("HOME must be set");
            let path = format!("{}/log/myapp-{}.log", home, val);
            let path = Path::new(&path);
            match File::create(&path) {
              Ok(mut f) => Box::new(f) as Box<Write>,
              Err(e) => {
                if format!("{}", e) == "No such file or directory (os error 2)" {
                  Box::new(io::stdout()) as Box<Write> // oh well
                } else {
                  panic!("Can't open log file: {}", e);
                }
              },
            }
          } else {
            Box::new(io::stdout()) as Box<Write>
          };
    
          // Commit the tx if we get these signals:
          let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]);
    
          let negotiator = OpenSsl::new().unwrap();
          let url = env::var("MYAPP_DATABASE").unwrap_or("postgres://myapp_test:secret@localhost:5432/myapp_test".to_owned());
          let tls = if url.contains("@localhost") { TlsMode::None }
                    else { TlsMode::Require(&negotiator) };
          let conn = Connection::connect(url, tls).expect("Can't connect to Postgres");
          let db = make_db_connection(&conn); // defines a bunch of prepared statements
          
          // now we can do stuff . . . 
    
        }
    

I would really like to have just this:

    
    
        let log = open_log();
        let db = prepare_db();
    

But those don't work, because all the temporary values are going to fall off
the stack when the helper functions return. I wish rust were smart enough to
make the functions put the values directly in the _caller_ 's stack frame.
Alternately, I wish rust would let me say that all those temporary values
should live as long as the returned thing (log and db), so it can keep them
around even if I don't have variables for them.

I thought maybe macros would help here, since there is no new stack frame, but
they still introduce a new scope that limits the lifetime of the temporary
variables.

Even worse, if I want to write tests for functions that use the log and db, I
need to repeat all that code again and again.

I think the answer is to use Box here? I haven't worked that out yet, but it
definitely feels harder than it should. And even if I can make it work, I'm a
little sad that I have to give up stack-based allocation.

I've also read that the answer might be OwningRef
([https://crates.io/crates/owning_ref](https://crates.io/crates/owning_ref)),
but I'm not sure yet. I wish the Rust book had a section about it. It seems
like Cow and Rc might also help me---I don't think so, but I'm not positive
yet. Covering these allocation-related crates in a systematic way would be
nice.

Anyway, I'm just a Rust newbie, but it sounds like the ergonomics effort is
(partly) for newbies like me, so I'm trying to express my struggles in terms
of a pattern that the Rust team could optimize for. It seems like something
that people would hit quite often. I'm sure there is an answer to what I'm
trying to do, so my point is that maybe it should be easier to find, or at
least better documented.

~~~
JoshTriplett
> Alternately, I wish rust would let me say that all those temporary values
> should live as long as the returned thing (log and db), so it can keep them
> around even if I don't have variables for them.

Possibly I've missed something critical about your example, but I think you
may want to create a struct Log, turn open_log() into Log::new(), and put the
things the log needs (such as the log file) inside Log, owned by Log.

~~~
pjungwir
So I passed out a lot of upvotes, but I thought I would add a thank you to you
and others trying to help me. :-)

I will try your suggestion re the log. The database example is trickier I
think since the prepared statements have references to `conn`, so it can't
move. Also it's annoying that I have to make `negotiator` even when I don't
need it.

------
alfiedotwtf
From moving from Perl to Rust, the _only_ thing that I miss is the postfix
"if":

    
    
      return true if number > 2;
    

VS:

    
    
      if number > 2 {
        return true
      }
    

I find that one-liners like these are _really_ ergonomic.

~~~
tedmielczarek
I have found using conditionals as expressions super useful, like:

``` fn foo(number: u32) -> bool { if number > 2 { true } else { false } } ```

I mean obviously in this situation you could just have the body of the
function be `number > 2` but I write a lot of Rust code that does similar
things now.

~~~
kbenson
In Perl, it's fairly idiomatic to use a postfix condition on a return like
that when doing _early_ return. Some of that is obviated by Rust being typed,
some is not. e.g.

    
    
        sub compute_interest( $amount, $interest ) {
            return $amount if $interest == 0; # Quick return
            die "We don't allow computation of negative interest rates" if $interest < 0; # Throw an exception
        
            # Do the actual work
            ...
        }
    

Edit: Also, it's worth noting that Perl enforces some behavior on this by only
allowing postfix conditionals to follow _a single statement_ , not blocks, so
it's not just a regular conditional with the order reversed.

~~~
kazagistar
I've always disliked this aspect of Perl... I prefer control flow to be
obvious.

~~~
AstralStorm
Hiding the actual control part on the right is pretty bad, yes.

Other than that, early returns can simplify flow a lot - otherwise you may
have to do massive nesting ifs or many flags. Or even goto or exceptions.

~~~
kbenson
It's less bad than it seems, since it's a common idiom in Perl, so you're used
to looking at it. It _can_ be quite bad if abused, but so can so much in Perl.

When used with a return or die (or my personal favorite for debugging with
'warn "FOO" if $ENV{DEBUG};"), the fact that flow is affected is obvious by
the very first characters in the statement, so it's obvious to then look for
when it applies.

Like so many features of languages, how it looks from the outside compared to
how it looks from those that are well versed in the language can be quite
different (not to say that everything that looks like a wart in Perl is okay
once you get used to it, every language has real warts). That's another aspect
to this whole thing, how much to you emphasize ergonomics that are primarily
for learning and novices. Features focused at novices to the expense of those
familiar with the language are interesting, because they may draw a lot of
people to your language, but you may not retain them very well.

------
EugeneOZ
Can't trust such ideas. Especially when I read about "conventions are good".
No, "conventions" is the plague and that example of mod.rs is an example of
crappy thing in Rust, difficult to gasp by a newbie. Exactly because it's just
"convention". Hope they will not destroy Rust by adding more "conventions" or
by switching to " implicity over explicity" camp of noobs.

