Skip to content

FromLiteral #143

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed

FromLiteral #143

wants to merge 7 commits into from

Conversation

XMPPwocky
Copy link
Contributor

literally the best RFC related to literals.

@mcpherrinm
Copy link

Using integer/float literals in a similar system may be useful for, eg, bigint libraries, or various integer-like types: Ones checking overflow, range-limited, etc.

@lilyball
Copy link
Contributor

This works for strings, but it definitely doesn't work for vector literals. The from_vector_literal() function is taking a &[T], which disallows moving the elements, so they must be copied or cloned. Even if that's solved, this still prevents the ability to use placement-new to construct the elements directly in the vector (not that we support this yet, but it's something we're going to support).

@lilyball
Copy link
Contributor

This also should cover numeric literals, as @mcpherrinm suggested.

For string literals, the proposed trait works. For vector literals, I think a trait needs to be designed that effectively does what the vec![] macro does today, which is initialize an empty collection (with a capacity) and then push (eventually emplace) each element expression in turn into it. For integer literals, I think we need support for something larger than int64; LLVM supports arbitrary-width integers, perhaps we should define some very large integer type as the type of integer literals and expose that to this trait. For floating point literals, maybe we can get away with not supporting that, otherwise I guess just stick with f64.

@XMPPwocky
Copy link
Contributor Author

If we would push each element onto a collection, perhaps FromVectorLiteral could take an Iterator, consuming it?

@huonw
Copy link
Member

huonw commented Jun 26, 2014

Please use markdown syntax, or else the RFC is not rendered nicely. This includes

Some `inline<code>`

```rust
fn rust_code_blocks() {
    for x in example {}
}
```

which renders as

Some inline<code>

fn rust_code_blocks() {
    for x in example {}
}

(This is particularly important for anything with <> in it, because that gets interpreted as HTML and consumed from the output, e.g. FromVectorLiteral<T> in the first paragraph of the rendered text is shown as just "FromVectorLiteral".)


Before, this compiles:

```let x = "Hello, world!".to_string();```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fences need to be on their own lines, i.e.

```
let x = "Hello, world!".to_string();
```

Markdown is hard without a preview.
@lilyball
Copy link
Contributor

@XMPPwocky How is the compiler supposed to generate an iterator? That also doesn't allow for placement new.

I think it has to look something like

trait FromVectorLiteral<T> {
    fn init(capacity: uint) -> Self;
    fn push(x: T);
}

with a vector literal expression that ends up being typed as something that conforms to this protocol evaluating to the equivalent of { let mut lit = FromVectorLiteral::init(3); lit.push(elem1); lit.push(elem2); lit.push(elem3); lit }.

As written this still doesn't support placement new, but that could be added once we have the capability of doing placement new.

@XMPPwocky
Copy link
Contributor Author

@kballard That looks workable. Another possibility, which translates directly to placement new, would be a method taking a size and returning a mutable slice. Of course, this burdens collections that are not contiguous in memory with the task of constructing a buffer and moving out of it, but it would be zero-cost for Vec and other contiguously-laid-out collections.

@lilyball
Copy link
Contributor

@XMPPwocky That approach is problematic when considering how to handle task failure in the middle of evaluating the vector literal. My general feeling is we should wait and see how Vec handles emplacing values and mirror that.

}
trait FromVectorLiteral<T> {

fn from_vector_literal(lit: &[T]) -> Self;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why &[T] and &'static str? Seems to me that either both should be 'static or neither.

@chris-morgan
Copy link
Member

I can imagine impl<K, V, T: MutableMap<K, V>> FromVectorLiteral<(K, V)> for T, which would allow HashMap literals with the syntax [(key, value), (key, value)].

I also agree with @kballard that something like init and push rather than taking a static slice is necessary.

@jsanders
Copy link

👍 to @mcpherrinm's suggestion. It's quite annoying to work with bigints without this.

@XMPPwocky
Copy link
Contributor Author

@kballard I think an underlying problem here is that fixed-size vectors can't have their length erased and then be moved out of (unless you go through Box and friends, thus incurring heap allocation for no good reason); I suspect FromVectorLiteral might want to be postponed until we have something like an "owning slice" (not Box, but something that could take a pointer and length of something on the stack and pass it down to other functions). Probably postponable to 1.0 at least, since vec! is sufficient for the most common case.

FromStringLiteral shouldn't be quite as much of a pain conceptually, and the "meow".to_string() menace must be stopped.

@lilyball
Copy link
Contributor

@XMPPwocky Even without their length being erased, you can't move out of a fixed-length vector multiple times. And even if you could, that's still the wrong approach, because it requires evaluating all the expressions before trying to construct the collection, which makes it impossible to use placement-new to construct the values inside the collection.

@ftxqxd
Copy link
Contributor

ftxqxd commented Jun 27, 2014

Many 👍s to the general idea (this is something I and probably many other people have wanted for a long time), and @mcpherrinm’s suggestion is certainly worth considering. (IMHO integer/float literals are more important/useful than vector literals.) But when I pictured custom literals, I always imagined it as being a compiler plugin (somewhat like a procedural macro). I’m not sure which is better—a trait is hugely easier to implement, but a compiler plugin is probably more flexible (and I believe it would resolve the problem of how FromVectorLiteral would work).

@bstrie
Copy link
Contributor

bstrie commented Jun 27, 2014

As embarrassed as I am at the look of "foo".to_string(), this sounds like a post-1.0 feature. If we decide to accept this but postpone it until then, then in the interim we could just hardcode support for String and Vec.

@XMPPwocky
Copy link
Contributor Author

@bstrie: I'd prefer doing nothing to hardcoding String and Vec. It's awkward enough having Box hardcoded, and there's at least a reason for that. Having String/Vec special-cased would annoy the low-level folks (who aren't using std) and generally be impure.

@emberian
Copy link
Member

+1 to no hardcoding.

@eddyb
Copy link
Member

eddyb commented Jun 27, 2014

The proper solution to moving in is &move [T]/Move<[T]>, not a Vec-like interface.

@bvssvni
Copy link

bvssvni commented Jun 27, 2014

+1

@XMPPwocky
Copy link
Contributor Author

@eddyb As kballard mentioned when I proposed that, it is not friendly with placement new.

A Vec-like interface is really growing on me, since data structures that are not contiguous in memory would work flawlessly (as would placement new), and judicious inlining should add no cost to data structures that are contiguous.


```rust
trait FromStringLiteral {
fn from_string_literal(lit: &'static str) -> Self;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that this really matters in an RFC, but Rust style is four spaces, no tabs.

@eddyb
Copy link
Member

eddyb commented Jun 27, 2014

@XMPPwocky well, here's the perfect placement new interface:

fn from_vector_literal<T, static N: uint>(f: impl FnOnce<(), [T, ..N]>) -> Self;

@Ape
Copy link

Ape commented Jun 27, 2014

C++11 uniform initialization (with initialization lists and constructors) is really useful and one of the features that still make me prefer C++ over Rust.

For example consider this simple example case:

std::map<std::string, std::vector<int>> playerScores = {
    {"Athos", {1, 4, 2}},
    {"Porthos", {}},
    {"Aramis", {9, 2}},
};

What is currently the best way to define a similar data structure in Rust? Would it be nicer with this new RFC?

@eddyb
Copy link
Member

eddyb commented Jun 27, 2014

With box overloading, the above could technically be translated to:

let player_scores: HashMap<String, Vec<int>> = box [
    (box "Athos", box [1, 4, 2]),
    (box "Porthos", box []),
    (box "Aramis", box [9, 2])
];

@Ape IIUC this RFC is similar, but without the explicit box keyword.

As an aside, I was originally quite hyped about box and the possibilities it could open (I am responsible for the choice of keyword, after all), but it seems that the ugly hack that is box(GC) expr may become the norm :(.


# Summary

Create new traits, FromStringLiteral, FromVectorLiteral<T>. Types implementing these traits can be automatically constructed from literals automatically. Literals will effectively have their actual types inferred statically.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"automatically constructed from literals automatically"

@Ericson2314
Copy link
Contributor

If/when Rust has a more complex notion of phases (thought I once read @pcwalton saying he wanted to do something like in Racket) this would all need to be revised. But that is probably a long way off. Just something to keep in mind if this is post 1.0 either way.

@XMPPwocky
Copy link
Contributor Author

@Ape @eddyb

Yes, this RFC would allow what eddyb mentioned. Note that Rust already supports the case of

struct Foo {
    x: [i32, ..4]
}
fn gimme_a_foo() -> Foo {
    Foo {x: [1, 2, 3, 4]}
}

It only works for arrays, though, because they are primitive types and have special syntax for it. This RFC extends this syntax to arbitrary types that opt-in to it.

@ftxqxd
Copy link
Contributor

ftxqxd commented Jun 28, 2014

One thing that doesn’t appear to be covered in the RFC is how the compiler would infer the type of a string/vector literal. Would it fall back to &'static str/[T, ..n] if it can’t figure out? Given that integer/float literal fallbacks were recently removed, introducing literal fallbacks for strings and vectors could definitely be considered a bad idea. Of course, the alternative would be to have no fallbacks, but then the extra cost of type annotations could outweigh the gains of not having to type vec!/.to_string(). This is one of the reasons I believe that adding custom integer/float literals would be better than adding string/vector literals.

One must also take into account what would happen in a case such as this:

fn str_fn<S: Str>(s: S) { ... }

str_fn("hello world");

Today, this works, but with the proposed change, even with a fallback to &'static str, the compiler would stumble trying to infer the type of s.

@XMPPwocky
Copy link
Contributor Author

@P1start I don't think the reason the int/float fallbacks were removed applies here. If you get the wrong numeric type in a case where the true type cannot be inferred, this is unlikely to actually fail to compile; instead it will appear to work, until you get unexpected overflow behavior at runtime. If you get an &'static str and try to call .append, that will not compile; in fact, I believe that all methods shared between &'static str and String behave identically; the same applies to [T, ..N] and Vec.

I think the second case could be soluble by enhancing inferrence to better support fallbacks with generics, but it is certainly a problem.

@lilyball
Copy link
Contributor

int/float fallbacks were removed because numeric primitives have limitations that aren't reflected in the type system, namely the bounds of the type and the resulting overflow behavior. So getting the wrong numeric type can lead to unexpected overflow. It also de-emphasizes int as the de-facto numeric type to use when you don't want to think about it. Also, part of the issue here is that int has different sizes on different platforms, so someone using it on a 64-bit platform because it's the default might end up behaving incorrectly on a 32-bit platform when it turns out they really need an i64.

None of that applies to values constructible from string/vector literals, so our current behavior for those literals is perfectly fine as a default.

fn from_string_literal(lit: &'static str) -> Self;
}

trait FromVectorLiteral<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be called FromArrayLiteral. Rust uses the following names:

  • [T, .. n] (fixed sized) array
  • &[T] slice
  • Vec<T> vector

@glaebhoerl
Copy link
Contributor

This kind of thing is OK in Haskell, but in Rust I think we should stick to the property that things are what they look like, i.e. what looks like a literal isn't actually a call to a user-defined function.

@eddyb
Copy link
Member

eddyb commented Jul 1, 2014

@glaebhoerl box has minimal visual overhead and would suffice (IMO) for the purpose of creating a dynamic container from a literal.

@glaebhoerl
Copy link
Contributor

@eddyb: At that point why not just use a macro? It doesn't work for string literals, but for e.g. Vec, vec! [1, 2, 3] is no worse visually than box [1, 2, 3] and less confusing conceptually. A hypothetical hash! could do even better (or is there one already?), not having to stick to array-of-tuples syntax.

@tailhook
Copy link

Is there enough of a use-case for a FromIntegerLiteral or FromFloatLiteral?

It would be very nice if python-style Decimal could be created like:

let val: Decimal = 1.20;

(i.e. without turning it into floating point first, and keeping precision)

@brson
Copy link
Contributor

brson commented Aug 7, 2014

Conversions and implicit conversions are in important ergonomic concern right now. We want to get the ergonomics here right without sacrificing Rust's other qualities like predictability.

This proposal has some shortcomings that lead us to think that it alone is not the answer: it's limited to two use cases while there are potentially others, the signatures are probably not correct, interaction with the compiler is unspecified (these two traits appear to be lang items). In general we want to put more and careful thought into this subject before moving forward, and there are several major language changes in the pipeline with higher priority.

Closing and postponing.

@brson brson closed this Aug 7, 2014
@brson brson added the postponed label Aug 7, 2014
@rust-highfive rust-highfive mentioned this pull request Sep 24, 2014
@Centril Centril added A-slice Slice related proposals & ideas A-conversions Conversion related proposals & ideas A-string Proposals relating to strings. labels Nov 26, 2018
wycats pushed a commit to wycats/rust-rfcs that referenced this pull request Mar 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-conversions Conversion related proposals & ideas A-slice Slice related proposals & ideas A-string Proposals relating to strings. postponed RFCs that have been postponed and may be revisited at a later time.
Projects
None yet
Development

Successfully merging this pull request may close these issues.