rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
97.96k stars 12.69k forks source link

Tracking issue for internal feature `no_core` #29639

Open aturon opened 8 years ago

aturon commented 8 years ago

The no_core feature allows you to avoid linking to libcore.

ahmedcharles commented 8 years ago

Perhaps it would be better to give this a 'positive' name, like [freestanding]?

eefriedman commented 8 years ago

no_core is basically useless on its own at the moment; you need lang_items to actually implement enough of the core traits to allow compiling anything non-trivial.

steveklabnik commented 7 years ago

Traige: no change

gnzlbg commented 5 years ago

no_core is basically useless on its own at the moment; you need lang_items to actually implement enough of the core traits to allow compiling anything non-trivial.

#[no_core] is super useful: some crates do not use any lang items and compile just fine (e.g. cfg-if), and if these crates aren't #[no_core] then we can't use them as dependencies of libcore, which means we have to duplicate them. For example, we currently duplicate cfg-if in libcore, libstd, stdsimd, and in libc (EDIT: and probably in hashbrown as well, that's 5 times that this macro is duplicated).

If we make cfg-if #[no_core] we can just add it as a normal cargo dependency to all those crates, and everything "just works". But we can't make it #[no_core] because it is not stable.

Ericson2314 commented 5 years ago

@gnzlbg makes a great point.

Additionally I'd like it if it was renamed to not mention the name of any create. If we get core depending on more crates, core might not seem so core-y. Something like no_dependencies is more future proof.

On the other hand, maybe we'll someday want to make primitives like float only available from come crate, removing them from the ambient prelude. That would mean the current name is more forward compatible after all.

kotauskas commented 4 years ago

This idea seems fishy and impractical for me, let me quote @Ericson2314 to elaborate why:

If we get core depending on more crates, core might not seem so core-y.

If core requires a certain crate for its convenience features and that crate is small enough to be used by core and to not depend on core at all, then why wouldn't core just include that crate into itself as an RFC to the language as a whole? If core depends on such a crate, therefore all of its members are linked to core and take the disk space in the final executable, meaning that not there's totally no reason to avoid using such a crate even if it's used only briefly in a very specific place where it's not that necessary which is why people rewrite a subset functionality from existing crates themselves for their specific use case.

This is not true for procedural macros, which are loaded into the compiler when core itself is compiled but are completely transparent to the final application, but either way there's the problem with the "standard" part in "standard library". The point of it is to provide utilities and core language features which are useful in a wide range of programs. Now, admittedly, cfg_if performs conditional compilation which is typically only used in abstractions over platform-specific code and that is supposed to implemented by a few crates and then reused by many other crates, meaning that the total amount of code which uses cfg_if is relatively low. But since it's such a small thing which was found useful in core, maybe it can become part of its public API, solving the duplication problem, providing access to its (unchanging!) interface for all Rust programs and promoting the use of it in a wider range of crates, improving readability. no_core would only promote hiding useful macros as private dependencies of core instead of promoting those macros becoming a part of Rust.

ojeda commented 3 years ago

If we make cfg-if #[no_core] we can just add it as a normal cargo dependency to all those crates, and everything "just works".

Please don't make core depend on anything unless there is a very good reason for it, much less on external repositories or on a particular build system.

joshtriplett commented 2 years ago

Marking as "needs-summary", because we need a closer look at the use cases for #[no_core], and why it is that core can't "compile to nothing" to serve some of those use cases (macro-only crates aside).

scottmcm commented 2 years ago

no_core implies so many things -- no Add, no Fn, etc -- that it doesn't seem like the right way to expose whatever the goals here are. To me, no_core is perma-unstable, as it's there for internal reasons so will stick around but won't be stable.

That's not to say that there couldn't be other things that would make sense to have on stable, like a "please don't use simd" kind of flag/attribute or "I wish LTO worked better to remove parts I don't need" or ..., but those wouldn't be this issue.

mamins1376 commented 2 years ago

In a recent embedded project, I needed to calculate CRC. I knew there is the crc crate which is quite minimal and meets every requirements.

The project is written mostly in C, so in order to save time, I exported a simple interface:

#[no_mangle]
unsafe extern "C" fn calculate_crc(data: *const u8, len: usize) -> u32 {
    ...
}

... and turned that into an object file with $ cargo rustc --release -- --emit=obj and simply linked it to the rest.

This had no dependency on core. Initially I declared the whole thing as #![no_core] but faced the mentioned missing lang items.

Does this count in any way?

luke-biel commented 2 years ago

no_core would defiintelly open up possibility of alternate core implementations. How useful would that be in current rust ecosystem - hard to tell. I could see this used in projects that require formal verification - eg. Ada limits it's stdlib for SPARK. But there may be better ways to achieve this.

gheoan commented 2 years ago

Should I be opening bug reports related to my use of no_core? I've seen confusing diagnostic messages and ICEs.

petrochenkov commented 2 years ago

@gheoan ICEs need to be reported and fixed in most cases, even in no_core.

As for diagnostics, I don't think we should do anything for no_core-specific diagnostics, unless it's zero cost in terms of code added to rustc.

eggyal commented 2 years ago

ICEs need to be reported and fixed in most cases, even in no_core.

Contradicts https://github.com/rust-lang/rust/issues/92495#issuecomment-1006193883:

Personally, I don't think ICEs with #![no_core] are worth addressing. It's a perma-unstable feature, and lang items are effectively internal compiler code that happens to be written in a crate. We don't try to prevent ICEs in the face of modifications to the compiler itself (in fact, many internal compiler APIs will deliberately ICE when they are used incorrectly).

The only correct way to use #![no_core] is to copy-paste all of the lang items, types, and impls that you're using (and even then, leaving out "unused" ones is pretty dubious).

scottmcm commented 2 years ago

I think getting ICEs in no_core for not providing all the things is absolutely to be expected, and not something that needs to be fixed. For example, u32 is Copy, and if you don't include that and thus get an ICE, too bad. We should not expend any effort at all to improve that situation.

tgross35 commented 2 years ago

I could see this used in projects that require formal verification - eg. Ada limits it's stdlib for SPARK. But there may be better ways to achieve this.

Imho, I think that it would be good to give Rust some hooks that would allow for use in safety-critical systems that could potentially eliminate the need for a separate core (for at least the basic SIL levels) - which would probably have benefits to the language as a whole. Some things I've considered that would help that -

The first two things are basically clippy lints, but they would need to be applied recursively to everything linked. Stack size analysis is another thing, but this is already sort of coming via stack_sizes. It would be interesting to reach out to Ferrous Systems and see what the people there have to say on the subject.

Regarding the cfg-if thing, is there any reasons the implementations aren't just exported from core?

joshlf commented 2 years ago

@kotauskas

If core requires a certain crate for its convenience features and that crate is small enough to be used by core and to not depend on core at all, then why wouldn't core just include that crate into itself as an RFC to the language as a whole? If core depends on such a crate, therefore all of its members are linked to core and take the disk space in the final executable, meaning that not there's totally no reason to avoid using such a crate even if it's used only briefly in a very specific place where it's not that necessary which is why people rewrite a subset functionality from existing crates themselves for their specific use case.

IIUC you might want to have core depend on an external crate for its implementation without exposing that crate's API in core's API. E.g., per @gnzlbg, "we currently duplicate cfg-if in libcore, libstd, stdsimd, and in libc" - even if we don't intend to expose cfg-if's API anywhere in the standard library, it would still be a boon to allow libcore, libstd, stdsimd, and libc to depend on a single crate rather than duplicating code across their implementations.

Cerber-Ursi commented 1 year ago

it would still be a boon to allow libcore, libstd, stdsimd, and libc to depend on a single crate rather than duplicating code across their implementations.

If I understand the argument correctly, it is essentially "except core, they all already depend on a single crate, namely core itself".

gorentbarak commented 11 months ago

I really don't understand why you would use this. A lot of people have given issues and said that "this isn't useful." But nobody has asked why you would even want not to use core. Is there a reason that I haven't realized? Why would I want to get rid of core, which has so many amazing features that do not depend on the operating system.

aeldidi commented 6 months ago

just writing here to throw in my $0.02. I’d like to avoid libcore in some places where I’d rather not be obligated to follow the license of the libcore code for some particular binary (this seems to indicate its licensed under MIT/Apache2).

Now in 99% of cases this is a complete non-issue, but surely it should at least be possible to write a (non-trivial) rust program without needing to do so? It’s not even really documented anywhere that an empty rust program with no_std still has a dependency on an MIT/Apache2 library, which might surprise some people, I’d say.

Aside from that (little bit of pedantry) though, I figure it’s at least worth having for completeness’s sake, as crates like cfg-if have shown might be useful. If some crate doesn’t depend on libcore, it should be able to express that. Keeping it as a permanently unstable feature might be wise, but it could be just as wise to stabilize it and epoch-bump whenever the feature is broken, seeing as it’s unlikely that would happen too often.

Either way, I think this is a good thing assuming it wouldn’t be a stability nightmare or anything.

Cerber-Ursi commented 6 months ago

Well, if we talk about licenses... are the compiler intrinsics distributed under MIT/Apache too?

aeldidi commented 6 months ago

LLVM has a specific exception for these

---- LLVM Exceptions to the Apache 2.0 License ----

As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.

In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.

(From LICENSE.txt in LLVM). Does Rust have some equivalent that I wasn't aware of? I couldn't find any such thing but that would make the licensing thing a non-issue.

tgross35 commented 6 months ago

LLVM is only distributed under the Apache 2.0 license, and excluded sections 4(a), 4(b) and 4(d) exempt the parts about needing to provide the license upon redistribution and giving attribution. Rust is dual licensed under either Apache or MIT at your choice https://github.com/rust-lang/rust/blob/43f4f2a3b1a3d3fb3dbbbe4fde33fb97c780ee98/COPYRIGHT#L16-L19. So de facto selecting to use core under the MIT license already means you don't have to worry about these attribution things, nor the GPL / patent provision / indemnity provision waiver.

It’s not even really documented anywhere that an empty rust program with no_std still has a dependency on an MIT/Apache2 library, which might surprise some people, I’d say.

Under what specific circumstances would this be surprising? Having the MIT license allows the source and software to be distributed liberally. Having no license is less free, unlicensed software cannot be distributed at all. LLVM intrinsics are still under the Apache2 license, just with some exceptions. Using Rust's core under MIT gets you roughly the same permissions as the modified Apache.

aeldidi commented 6 months ago

So de facto selecting to use core under the MIT license already means you don't have to worry about these attribution things

This isn't correct. The MIT license definitely requires attribution and including a copy of the license with distribution.

Under what specific circumstances would this be surprising?

Under the circumstances that both gcc and clang both have exceptions so that this isn't the case. The norm for people coming from C/C++ is that "compiling without the standard library means you don't have to worry about licenses", and it's surprising that rust differs in this way.

Having no license is less free, unlicensed software cannot be distributed at all. LLVM intrinsics are still under the Apache2 license, just with some exceptions. Using Rust's core under MIT gets you roughly the same permissions as the modified Apache.

Again: the differences granted by the exception are what I'm after here. Whether or not something is licensed under Apache 2 or MIT, the point is whether or not the user has some hidden obligation to give a copy of the license and give attribution when it's not obvious. With both gcc and clang, these exceptions mean that someone compiling with -nostdlib doesn't have to worry about producing any licenses or giving attribution at all, which I was trying to say would be nice to have in rust, since It's a little odd that without no_core every rust program requires giving attribution and a copy of some license.

The proposed solution here isn't "no license is better", just that having no_core or introducing some linking exception for libcore would be nice.

tgross35 commented 6 months ago

This isn't correct. The MIT license definitely requires attribution and including a copy of the license with distribution.

I am not a lawyer, but I believe the clause The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software is usually interpreted as "Software" referring to the library itself (i.e. Rust as a whole), but anything built with it is not considered a "copy" or "substantial portion".

Whether this is the correct way to interpret it or not I do not know. The question really isn't related to no_core, so could you bring this up on Zulip?

aeldidi commented 6 months ago

I agree that the specific issue on what the MIT license requires in binary distribution is off topic, I'll continue that discussion elsewhere.

LegNeato commented 6 months ago

I think I have a use-case for this. I want to specify a logical data model using Rust's syntax and type system so that implementations can map in and out (a la serde). It is important to keep it zero-sized to maintain maximum flexibility for both the people mapping in and out as well as the end users relying on those crates.

I want to mark my definition crate as #[no_core] so the compiler automatically guarantees I don't accidentally depend on a specific storage implementation detail such as u64.

I can achieve my overhead and compatibility goals by sticking to const, but a) there's no guarantee that const happens at compile time and b) even if it was guaranteed to run at compile time, there is not something like #[const_only] to have the compiler make it so I don't inadvertently introduce something non-const wrapping the const in the future.

scottmcm commented 6 months ago

I want to mark my definition crate as #[no_core] so the compiler automatically guarantees I don't accidentally depend on a specific storage implementation detail such as u64.

u64 is a magic primitive, not something from core, so it'll still be there even in no_core. (And the correct way to do type restrictions is with traits, not trying to remove them from existence.)

LegNeato commented 6 months ago

u64 is a magic primitive, not something from core, so it'll still be there even in no_core. (And the correct way to do type restrictions is with traits, not trying to remove them from existence.)

Right, but if I make a NoCore trait I can't tell the compiler to enforce that I am not using anything from core either in the trait or for my items that impl it.

One could informally not use std and have the same observable effects (minus linked std which might be stripped), but the existence of an optional #[no_std] is a strictly better alternative as invariants are enforced by the compiler. I don't see how core is different, other than the fact that there are fewer uses of #[no_core].