-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Allow non-ASCII identifiers #2457
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
Changes from 2 commits
ec728b3
4c1bda9
619f5b4
142d0bc
6b2a94a
3e19d26
a4830a1
12d0623
79bbc8e
41f0723
3c96d81
940dab5
da43d09
0e0ca66
935c917
8d548d4
9356fc1
40d53f5
7732810
e3f3692
d389a9c
70297a9
9bf90df
a6da03a
c4dff64
0c78631
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
- Feature Name: unicode_idents | ||
- Start Date: 2018-06-03 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Allow non-ASCII letters (such as accented characters, Cyrillic, Greek, Kanji, etc.) in Rust identifiers. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
Rust is written by many people who are not fluent in the English language. Using identifiers in ones native language eases writing and reading code for these developers. | ||
|
||
The rationale from [PEP 3131] nicely explains it: | ||
|
||
> ~~Python~~ *Rust* code is written by many people in the world who are not familiar with the English language, or even well-acquainted with the Latin writing system. Such developers often desire to define classes and functions with names in their native languages, rather than having to come up with an (often incorrect) English translation of the concept they want to name. By using identifiers in their native language, code clarity and maintainability of the code among speakers of that language improves. | ||
> | ||
> For some languages, common transliteration systems exist (in particular, for the Latin-based writing systems). For other languages, users have larger difficulties to use Latin to write their native words. | ||
|
||
Additionally some math oriented projects may want to use identifiers closely resembling mathematical writing. | ||
|
||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
Identifiers include variable names, function and trait names and module names. They start with a letter or an underscore and may be followed by more letters, digits and some connecting punctuation. | ||
|
||
Examples of valid identifiers are: | ||
|
||
* English language words: `color`, `image_width`, `line2`, `Photo`, `_unused`, ... | ||
* ASCII words in foreign languages: `die_eisenbahn`, `el_tren`, `artikel_1_grundgesetz` | ||
|
||
* words containing accented characters: `garçon`, `hühnervögel` | ||
* identifiers in other scripts: `Москва`, `東京`, ... | ||
|
||
Examples of invalid identifiers are: | ||
|
||
* Keywords: `impl`, `fn`, `_` (underscore), ... | ||
* Identifiers starting with numbers or containing "non letters": `42_the_answer`, `third√of7`, `◆◆◆`, ... | ||
|
||
* Emojis: 🙂, 🦀, 💩, ... | ||
|
||
|
||
Similar Unicode identifiers are normalized: `a1` and `a₁` (a<subscript 1>) refer to the same variable. This also applies to accented characters which can be represented in different ways. | ||
|
||
To disallow any Unicode identifiers in a project (for example to ease collaboration or for security reasons) limiting the accepted identifiers to ASCII add this lint to the `lib.rs` or `main.rs` file of your project: | ||
|
||
```rust | ||
#![forbid(unicode_idents)] | ||
|
||
``` | ||
|
||
Some Unicode character look confusingly similar to each other or even identical like the Latin **A** and the Cyrillic **А**. The compiler may warn you about easy to confuse names in the same scope. If needed (but not recommended) this warning can be silenced with a `#[allow(confusable_unicode_idents)]` annotation on the enclosing function or module. | ||
|
||
## Usage notes | ||
|
||
All code written in the Rust Language Organization (*rustc*, tools, std, common crates) will continue to only use ASCII identifiers and the English language. | ||
|
||
For open source crates it is recommended to write them in English and use ASCII-only. An exception should be made if the application domain (e.g. math) benefits from Unicode and the target audience (e.g. for a crate interfacing with Russian passports) is comfortable with the used language and characters. Additionally crates should provide an ASCII-only API. | ||
|
||
Private projects can use any script and language the developer(s) desire. It is still a good idea (as with any language feature) not to overuse it. | ||
|
||
|
||
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
Identifiers in Rust are based on the [Unicode® Standard Annex #31 Unicode Identifier and Pattern Syntax][TR31]. Rust compilers shall use at least Revision 27 of the standard. | ||
|
||
|
||
The lexer defines identifiers as: | ||
|
||
> **<sup>Lexer:<sup>** | ||
> IDENTIFIER_OR_KEYWORD: | ||
> XID_Start XID_Continue<sup>\*</sup> | ||
|
||
> | `_` XID_Continue<sup>+</sup> | ||
|
||
> | ||
> IDENTIFIER : | ||
> IDENTIFIER_OR_KEYWORD <sub>*Except a [strict] or [reserved] keyword*</sub> | ||
|
||
`XID_Start` and `XID_Continue` are used as defined in the aforementioned standard. The definition of identifiers is forward compatible with each successive release of Unicode as only appropriate new characters are added to the classes but none are removed. | ||
|
||
Two identifiers X, Y are considered to be equal if there [NFKC forms][TR15] are equal: NFKC(X) = NFKC(Y). | ||
|
||
|
||
A `unicode_idents` lint is added to the compiler. This lint is `allow` by default. The lint checks if any identifier in the current context contains a codepoint with a value equal to or greater than 0x80 (outside ASCII range). Not only locally defined identifiers are checked but also those imported from other crates and modules into the current context. | ||
|
||
|
||
## Confusable detection | ||
|
||
Rust compilers should detect confusingly similar Unicode identifiers and warn the user about it. | ||
|
||
Note: This is *not* a mandatory for all Rust compilers as it requires considerable implementation effort and is not related to the core function of the compiler. It rather is a tool to detect accidental misspellings and intentional homograph attacks. | ||
|
||
A new `confusable_unicode_idents` lint is added to the compiler. The default setting is `warn`. | ||
|
||
Note: The confusable detection is set to `warn` instead of `deny` to enable forward compatibility. The list of confusable characters will be extended in the future and programs that were once valid would fail to compile. | ||
|
||
The confusable detection algorithm is based on [Unicode® Technical Standard #39 Unicode Security Mechanisms Section 4 Confusable Detection][TR39Confusable]. For every distinct identifier X in the current scope execute the function `skeleton(X)`. If there exist two distinct identifiers X and Yin the same crate where `skeleton(X) = skeleton(Y)` report it. | ||
|
||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
* "ASCII is enough for anyone." As source code should be written in English and in English only (source: various people) no charactes outside the ASCII range are needed to express identifiers. Therefore support for Unicode identifiers introduces unnecceray complexity to the compiler. | ||
|
||
* "Foreign characters are hard to type." Usually computer keyboards provide access to the US-ASCII printable characters and the local language characters. Characters from other scripts are difficult to type, require entering numeric codes or are not available at all. These characters either need to be copy-pasted or entered with an alternative input method. | ||
* "Foreign characters are hard to read." If one is not familiar with the characters used it can be hard to tell them apart (e.g. φ and ψ) and one may not be able refer to the identifiers in an appropriate way (e.g. "loop" and "trident" instead of phi and psi) | ||
* "My favorite terminal/text editor/web browser" has incomplete Unicode support." Even in 2018 some characters are not widely supported in all places where source code is usually displayed. | ||
* Homoglyph attacks are possible. Without confusable detection identifiers can be distinct for the compiler but visually the same. Even with confusable detection there are still similar looking characters that may be confused by the casual reader. | ||
|
||
# Rationale and alternatives | ||
[alternatives]: #alternatives | ||
|
||
As stated in [Motivation](#motivation) allowing Unicode identifiers outside the ASCII range improves Rusts accessibility for developers not working in English. Especially in teaching and when the application domain vocabulary is not in English it can be beneficial to use names from the native language. To facilitate this it is necessary to allow a wide range of Unicode character in identifiers. The proposed implementation based on the Unicode TR31 is already used by other programming languages (e.g. Python 3) and is implemented behind the `non_ascii_idents` in *rustc* but lacks the NFKC normalization proposed. | ||
|
||
Possible variants: | ||
|
||
1. Require all identifiers to be in NFKC or NFC form. | ||
2. Two identifiers are only equal if their codepoints are equal. | ||
3. Perform NFC mapping instead of NFKC mapping for identifiers. | ||
4. Only a number of common scripts could be supported. | ||
|
||
5. A [restriction level][TR39Restriction] is specified allowing only a subset of scripts and limit script-mixing within an identifier. | ||
|
||
An alternative design would use [Immutable Identifiers][TR31Alternative] as done in [C++]. In this case a list of Unicode codepoints is reserved for syntax (ASCII operators, braces, whitespace) and all other codepoints (including currently unassigned codepoints) are allowed in identifiers. The advantages are that the compiler does not need to know the Unicode character classes XID_Start and XID_Continue for each character and that the set of allowed identifiers never changes. It is disadvantageous that all not explicitly excluded characters at the time of creation can be used in identifiers. This allows developers to create identifiers that can't be recognized as such. It also impedes other uses of Unicode in Rust syntax like custom operators if they were not initially reserved. | ||
|
||
It always a possibility to do nothing and limit identifiers to ASCII. | ||
|
||
It has been suggested that Unicode identifiers should be opt-in instead of opt-out. The proposal chooses opt-out to benefit the international Rust community. New Rust users should not need to search for the configuration option they may not even know exists. Additionally it simplifies tutorials in other languages as they can omit an annotation in every code snippet. | ||
|
||
## Confusable detection | ||
|
||
The current design was chosen because the algorithm and list of similar characters are already provided by the Unicode Consortium. A different algorithm and list of characters could be created. I am not aware of any other programming language implementing confusable detection. The confusable detection was primarily included because homoglyph attacks are a huge concern for some member of the community. | ||
|
||
Instead of offering confusable detection the lint `forbid(unicode_idents)` is sufficient to protect project written in English from homoglyph attacks. Projects using different languages are probably either written by students, by a small group or inside a regional company. These projects are not threatened as much as large open source projects by homoglyph attacks but still benefit from the easier debugging of typos. | ||
|
||
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
"[Python PEP 3131][PEP 3131]: Supporting Non-ASCII Identifiers" is the Python equivalent to this proposal. The proposed identifier grammar **XID_Start XID_Continue<sup>\*</sup>** is identical to the one used in Python 3. | ||
|
||
[JavaScript] supports Unicode identifiers based on the same Default Identifier Syntax but does not apply normalization. | ||
|
||
The [CPP reference][C++] describes the allowed Unicode identifiers it is based on the immutable identifier principle. | ||
|
||
[Java] also supports Unicode identifiers. Character must belong to a number of Unicode character classes similar to XID_start and XID_continue used in Python. Unlike in Python no normalization is performed. | ||
|
||
The [Go language][Go] allows identifiers in the form **Letter (Letter | Number)\*** where **Letter** is a Unicode letter and **Number** is a Unicode decimal number. This is more restricted than the proposed design mainly as is does not allow combining characters needed to write some languages such as Hindi. | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
* Which context is adequate for confusable detection: file, current scope, crate? | ||
* Are Unicode characters allowed in `no_mangle` and `extern fn`s? | ||
* How do Unicode names interact with the file system? | ||
* Are crates with Unicode names allowed and can they be published to crates.io? | ||
* Are `unicode_idents` and `confusable_unicode_idents` good names? | ||
* Should [ZWNJ and ZWJ be allowed in identifiers][TR31Layout]? | ||
* Should *rustc* accept files in a different encoding than *UTF-8*? | ||
|
||
|
||
[PEP 3131]: https://www.python.org/dev/peps/pep-3131/ | ||
[TR15]: https://www.unicode.org/reports/tr15/ | ||
[TR31]: http://www.unicode.org/reports/tr31/ | ||
[TR31Alternative]: http://unicode.org/reports/tr31/#Alternative_Identifier_Syntax | ||
[TR31Layout]: https://www.unicode.org/reports/tr31/#Layout_and_Format_Control_Characters | ||
[TR39Confusable]: https://www.unicode.org/reports/tr39/#Confusable_Detection | ||
[TR39Restriction]: https://www.unicode.org/reports/tr39/#Restriction_Level_Detection | ||
[C++]: https://en.cppreference.com/w/cpp/language/identifiers | ||
[Julia Unicode PR]: https://github.com/JuliaLang/julia/pull/19464 | ||
[Java]: https://docs.oracle.com/javase/specs/jls/se10/html/jls-3.html#jls-3.8 | ||
[JavaScript]: http://www.ecma-international.org/ecma-262/6.0/#sec-names-and-keywords | ||
[Go]: https://golang.org/ref/spec#Identifiers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some languages use U+200C ZERO WIDTH NON-JOINER and U+200D ZERO WIDTH JOINER, which are not in XID_Continue. Should they be allowed too? See section 2.3 of UAX #31.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To have proper support then they should be allowed, as it affects the rendering of text, which may cause it to have different meanings (I'm not sure, but it seems likely to me). However, this can cause issues like in C++ and Swift for example, different number of zero-width joiners or spaces are different identifiers, and that leads to a readability nightmare.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is why UAX #31 suggests allowing them only in certain positions, to avoid having multiple distinct identifiers look identical.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So AIUI these are used for:
Forcing explicit viramas in Indic scripts in consonant clusters. There is a semantic difference but it's exceedingly minor and mostly comes in play for Sanskrit. It's a pretty minor difference; AIUI it's used in certain kinds of compound words and is very easily omitted
Certain vowel presentational forms in Bengali and Oriya. Also minor.
Forcing letters to take different (word-medial, etc) forms in the Perso-Arabic script, used for:
Arabic affixes, when shown in isolation or when used with non-arabic words. Imagine you had to write something like "Rust's" where "Rust" is in the Latin script but the 's is in Arabic, you need the Arabic suffix to not use a word-initial text form. An example of this is the ب prefix preposition
Arabic abbreviations
AFAICT these are all somewhat optional (though preferred). The abbreviations one might be the most used.
(that said, there are much larger problems with using an RTL script in rust)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ZWNJ has important use in Persian at least https://en.m.wikipedia.org/wiki/Zero-width_non-joiner