Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign up`extern type` cannot support `size_of_val` and `align_of_val` #49708
Comments
joshtriplett
added
the
T-lang
label
Apr 5, 2018
This comment has been minimized.
This comment has been minimized.
|
@rfcbot fcp merge |
This comment has been minimized.
This comment has been minimized.
rfcbot
commented
Apr 5, 2018
•
|
Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged teams: No concerns currently listed. Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
rfcbot
added
the
proposed-final-comment-period
label
Apr 5, 2018
This comment has been minimized.
This comment has been minimized.
will the lint be emitted before or after monomorphization? P.S. cc rust-lang/rfcs#2310 |
This comment has been minimized.
This comment has been minimized.
|
I'm specifically in favor of "before", only emitting anything after monomorphization under some general umbrella of "this code will always panic at runtime" warnings. |
This comment has been minimized.
This comment has been minimized.
|
It would be nice to handle generic functions that get monomorphized with an |
This comment has been minimized.
This comment has been minimized.
|
The RFC text (and the discussion even more so, IIRC) mention the possibility of adding a new trait for encoding this constraint. I assume this has been dropped because that trait would have to be included in bounds by default like |
This comment has been minimized.
This comment has been minimized.
|
@rkruppe yes sadly.... rust-lang/rfcs#2310 was opened as backup plan. See the end of #43467. |
This comment has been minimized.
This comment has been minimized.
|
I'm a bit confused why this is in rust-lang/rust instead of rust-lang/rfcs. (I have rust-lang/rfcs set as watched, so I get email copies of all issues and PRs there, but not here.) |
This comment has been minimized.
This comment has been minimized.
|
Because it's a bug about |
nikomatsakis
referenced this issue
Apr 10, 2018
Open
Tracking issue for RFC 1861: Extern types #43467
This comment has been minimized.
This comment has been minimized.
Because this is not a new RFC, it's nailing down a specific unresolved question around extern types (though we should have linked from the tracking issue to here -- fixed). This is pretty standard practice. |
This comment has been minimized.
This comment has been minimized.
|
Why lint when you can have type safety? Two convincing comments from the other discussion thread:
And my own perspective: there are various places where I'd like to be generic over things that the language doesn't allow. That goes hand in hand with the type system. For example, you can't be generic over lengths when using arrays or calling conventions when using function pointers. Let's not add more features in that could reasonably be described correctly in the type system but aren't. |
This comment has been minimized.
This comment has been minimized.
If the lint is deny-by-default (which I think it should be), it'd effectively act the same as a type error. The second comment you quote seems based on the idea that it'd be a warning and not an error. So the only difference is whether to provide a more general trait-based mechanism or to handle the specific case at hand. I haven't seen any fundamental opposition to the idea of introducing those traits in the future if we have a more general case that needs them, but if we want those traits for some future features that we haven't yet introduced to the language, let's design those traits alongside those future language features. |
This comment has been minimized.
This comment has been minimized.
|
Would it be possible to make substituting an // ok:
fn f1(a: &ExternType) {}
fn f2() -> *mut ExternType {}
fn f3<T: ?Sized>(a: &T, b: *const ExternType) {}
fn f4<A, R, F: Fn(A) -> R>(func: F, a: A) -> R {}
let w1 = size_of::<*const ExternType>();
let w2 = size_of_val(&f2());
let w3 = f4(|x| x, &*f2());
//^ substituting `&ExternType` is ok.
// errors (gated):
fn g1<T: ?Sized>(a: &T) {}
g1(&*f2()); //~ ERROR: Cannot substitute an extern type to `T`
fn g2() -> Box<ExternType> {} //~ ERROR: Cannot substitute an extern type to `T`
struct S1(ExternType); //~ ERROR: Cannot use an extern type as struct field.
struct S2<R: ?Sized>(R);
let s2: S2<ExternType>; //~ ERROR: Cannot substitute an extern type to `R`
struct S3;
impl Deref for S3 {
type Target = ExternType; //~ ERROR: Cannot use extern type as associated type.
fn deref(&self) -> &ExternType {}
} |
This comment has been minimized.
This comment has been minimized.
In terms of trust, only lints that cannot be turned off allow foreign code to be trusted to the same degree. The generics thing that @jethrogb points out is good too. Let's assume we have a monomorphization-time lint as you proposed, @joshtriplett (I agree with you that that's better than only having a pre-monomorphization time lint). Then, even thought its just as safe (same things will be prevented in the end), the user experience is still worse. The library could be fine over it's testsuite, but not fine when instantiated downstream. And for upstream, even if the test do catch such bad instantiations, type errors pop up sooner than monomorization-time lints for a quicker debug cycle. |
This comment has been minimized.
This comment has been minimized.
|
@kennytm I mentioned before in a buried comment that we can stabilize In many ways, that is effectively the same as what you propose for stable code, unstable |
This comment has been minimized.
This comment has been minimized.
|
@Ericson2314 That's a much bigger change because you need to introduce a new concept Furthermore we are not even committed to whether |
This comment has been minimized.
This comment has been minimized.
|
@kennytm
Nothing I propose forces a final decision, since
|
This comment has been minimized.
This comment has been minimized.
|
How does this relate to #48055? It seems like that is another case where we'd assume for arbitrary |
This comment has been minimized.
This comment has been minimized.
|
Well, not necessarily out of the box, without a way to get an owned Anyway, definitely seems like a good idea to restrict that feature to |
nikomatsakis
referenced this issue
Apr 26, 2018
Closed
RFC: DynSized without ?DynSized — Lint against use of `extern type` in `size_of_val`, and more #2310
This comment has been minimized.
This comment has been minimized.
briansmith
commented
May 17, 2018
I propose that it be (1) deny-by-default, (2) not allowed to be changed from that default, and (3) formatted to look like a type error instead of a lint message. Then it would be indistinguishable, to the user, from a type error. |
Centril
added
the
disposition-merge
label
May 24, 2018
rfcbot
added
final-comment-period
and removed
proposed-final-comment-period
labels
Jun 22, 2018
This comment has been minimized.
This comment has been minimized.
rfcbot
commented
Jun 22, 2018
|
|
This comment has been minimized.
This comment has been minimized.
briansmith
commented
Jun 22, 2018
|
I still think that when we have the option of enforcing a typing rule in the type checker vs. the lint mechanism, we should enforce it in the type checker. I understand and don't necessarily disagree with the objections to |
This comment has been minimized.
This comment has been minimized.
|
There is precedent for such no-so-elegant special-casing: instanciating |
This comment has been minimized.
This comment has been minimized.
|
Hmmm on the other hand to avoid monomorphization-time errors |
This comment has been minimized.
This comment has been minimized.
|
However, the transmute special casing is sound, since transmute flat out refuses to work work on type parameters. Edit: this assumes we don't want monomorphization-time errors, but those are pretty firmly established as a big no-no at this point. |
This comment has been minimized.
This comment has been minimized.
|
#![crate_type = "lib"]
use std::mem::transmute;
fn g<T>(a: [[T; 2]; 2]) -> [T; 4] {
unsafe { transmute(a) }
}
The same cannot be applied to unsafe fn reinterpret_as_bytes<T: ?Sized>(a: &T) -> &[u8] {
let size = size_of_val(a);
//~^ ERROR
// we don't know `T` would be an `extern type` without monomorphizing
slice::from_raw_parts(a as *const T as *const u8, size)
} |
This comment has been minimized.
This comment has been minimized.
|
Has any consideration been given to @kennytm's suggestion of disallowing extern types in generics? Pointers to extern types would work just fine as type arguments and associated types, since they are |
This comment has been minimized.
This comment has been minimized.
|
@mikeyhew Note that adding an unstable opt-out [1]: e.g. "shallow" because if you push the type parameter deeper, e.g. |
This comment has been minimized.
This comment has been minimized.
briansmith
commented
Jun 23, 2018
•
The current proposal is to have part of the type checker moved to the lint mechanism. Thus we can say "look at how simple and clear the type checker is" and "look at how consistent* the type system is" when we want to think those things because we just say "the lint mechanism isn't part of the type checker so don't look at it." Then on the other hand we'd say "we still check this statically" because the lint mechanism does the type checking that was left out of the type checker. However, the value in putting part of the type checker into the lint checker is purely aesthetic: the fact is that the type system isn't really consistent: using these intrinstics on My understanding is that the code generator has to be specialized to generate the I understand that people don't want the type checker to reject things after monomorphization but in a programmer's actual usage of this stuff, it makes no difference whether the type checker rejects the program after monomorphization or the linter rejects the program after monomorphization. The program is rejected either way. * I'm not sure "consistent" is the best word. |
This comment has been minimized.
This comment has been minimized.
It is a programmer error, but for every error we (the designers of the relevant language feature or API) have the choice of which mechanisms we use to combat that error. The type system is a useful tool for that, but it's not the only one, and in this case it has drawbacks (the drawbacks of momonomorphization time errors, if we restrict ourselves to your proposal rather than the one @mikeyhew brought up) and few upsides. Additionally, even if the type system and lint approaches have equivalent complexity, the cost of a complex lint is far smaller than the cost of a type system feature of equivalent complexity. Lints are optional, their existence and how they work exactly is a quality-of-implementation issue. They can have bugs without causing UB (if false negative) or hard-breaking correct code (if false positive). They don't have to be specified in as much detail or be reproduced exactly in other implementations. They can be ignored in efforts to verify the language or the compiler.
The code generator complexity seems quite manageable to me. Even if emitting a panic in codegen directly is problematic (it might, since we don't currently have any intrinsics that can panic AFAIK), we can achieve the same effect by adding a trivial parametricity-breaking intrinsic fn size_of_val<T: ?Sized>(val: &T) -> usize {
if is_extern_type::<T>() {
panic!(...)
} else {
intrinsics::size_of_val(val)
}
}The intrinsic would continue to return 0 or some other nonsense for extern types, but it's perma-unstable anyway.
I see why you're putting it that way, but from my perspective it's quite the opposite: making this a lint is an admission that our type system isn't covering this class of errors! We're just making the conscious decision to have a less powerful type system in pursuit of other goals.
One disadvantage of monomorphization time errors is that a buggy library can cause hard errors when compiling client code with no way to work around it other than patching the library. In contrast, a deny-by-default lint can be turned off and is "capped" by Cargo when compiling dependencies, so if the buggy code is never actually executed, one can still productively use the rest of the library. This is similar to how the compiler reports runtime panics that it can identify by constant evaluation, but explicitly isn't allowed to actually break the build in those cases. |
This comment has been minimized.
This comment has been minimized.
As another proposal, the intrinsic could actually return |
This comment has been minimized.
This comment has been minimized.
|
What would the compiler generate when computing the offset of an unsized field, which involves |
This comment has been minimized.
This comment has been minimized.
|
Oh, right -- sorry I forgot about that. I don't think a dummy value would be a good idea though. |
This comment has been minimized.
This comment has been minimized.
You're right. I was saying we disallow Keeping the IIRC, the problems with
Whatever we end up doing, these are the things we need to disallow, at minimum:
|
This comment has been minimized.
This comment has been minimized.
|
Regarding the compile-time lint, I'd just like to highlight my suggestion from a different thread for how it could be implemented as a more general feature: In short, right now putting |
This comment has been minimized.
This comment has been minimized.
briansmith
commented
Jun 24, 2018
•
|
Given a value |
This comment has been minimized.
This comment has been minimized.
briansmith
commented
Jun 24, 2018
•
|
In other words, how can I write this function: fn checked_size_of_val<T>(x: T) -> Option<usize> {
if [would `size_of_val(x)` panic?] {
None
} else {
Some(size_of_val(x)
}
}In particular, how can we make this function work in the case where panic=abort? |
XAMPPRocky
added
the
C-enhancement
label
Jun 29, 2018
This comment has been minimized.
This comment has been minimized.
briansmith
commented
Jun 29, 2018
|
One thing that's mentioned in the comment above that didn't get enough discussion, IMO, is the suggestion to just deprecate and remove |
This comment has been minimized.
This comment has been minimized.
|
They are used in multiple places of the standard library, in generic code that supports |
This comment has been minimized.
This comment has been minimized.
That's not the whole truth--every practical C++ compiler does support nonstandard intrinsics with those functions. |
This comment has been minimized.
This comment has been minimized.
|
@whitequark What intrinsics are you referring to? I imagine the equivalent in C++ would be something like class Base {
int a;
};
class Derived : public Base {
int b;
};
void foo() {
Base *obj = new Derived;
size_t size = get_real_size(obj);
// ^^^^^^^^^^^^^ some intrinsic?
assert(size == sizeof(Derived));
assert(size != sizeof(Base));
}However, I've never heard of functionality that lets you do this. If you have RTTI enabled you can get a (If the object was allocated on the heap, there are nonstandard allocator functions that an approximation of the allocated size, like |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@whitequark I don't think #include <iostream>
using namespace std;
struct SuperClass {
virtual void some_method() {}
};
struct SubClass : public SuperClass {
int field;
};
int main() {
SubClass foo;
SuperClass& bar = foo;
cout << "sizeof(foo) = " << sizeof(foo) << endl;
// sizeof(foo) = 16
cout << "sizeof(bar) = " << sizeof(bar) << endl;
// sizeof(bar) = 8
} |
This comment has been minimized.
This comment has been minimized.
|
Oh I see, thanks for explanation and sorry for misleading comment. |
rfcbot
added
the
finished-final-comment-period
label
Jul 2, 2018
This comment has been minimized.
This comment has been minimized.
rfcbot
commented
Jul 2, 2018
|
The final comment period, with a disposition to merge, as per the review above, is now complete. |
joshtriplett commentedApr 5, 2018
Based on discussion on #43467 and in @rust-lang/lang meetings, an opaque
extern typetype cannot supportsize_of_valoralign_of_val. We believe that we should panic or abort in this case. We also believe that we should have a compile-time lint that detects this whenever possible, since at some level the compiler should know at compile time if code invokessize_of_valoralign_of_valon anextern type.I've opened this separate issue to document that decision, and will propose FCP on it to give time for final comments.