Jump to page: 1 25  
Page
Thread overview
January 19

This is a semi rant born from frustration. Currently I'm trying to figure out Mir Ion so I can serialize some data, and oh boy is it frustrating.

The entire library is annotated out the wazoo with @safe and @nogc and pure and inout. Its a serialization library so as you might imagine it's doing reflection on types, calling member functions to try and serialize stuff, things like that. I have spent hours now adding attributes and casting attributes away. Writing @trusted wrapper types for std.algo ranges. Trying to decipher the vomit that dmd gives when one the types is not quite right. All I want to do is serialize a list of structs from a range and be able to deserialize them back but I can't do that because I still have not gotten it working.

It's actually maddening, I have spent 2 days on this. What value are all these attributes adding? Because it's a useability nightmare. Every time I encounter a D library that tries to be clever with these annotations, it's just absolutely made things more complex and less useable. Same story every time I have ever tried to use them in my own code.

January 18
You can pretty much not use any attributes if you use none (including not using @safe).

We can help if you can present specific cases.
January 19
On 19/01/2023 3:10 PM, Walter Bright wrote:
> You can pretty much not use any attributes if you use none (including not using @safe).
> 
> We can help if you can present specific cases.

If you call code that is annotated, that then calls your code this is not true.

Callbacks, hooks on types as used with serialization ext. don't really work here currently.

There is a reason why I've long wanted @localnogc, but yeah... pure and @safe also need local versions too.
January 19
On Thursday, 19 January 2023 at 02:10:14 UTC, Walter Bright wrote:
> You can pretty much not use any attributes if you use none (including not using @safe).
>
> We can help if you can present specific cases.

In this case you can't leave them unannotated as all the Mir functions are annotated and they try to call stuff in the type you are trying to serialize.

@safe and const are the one that is the most problematic in my case. (I mentioned @nogc before but I listed it by mistake but I have ran into similar issues with @nogc libs)

I am not really looking for help with Mir Ion specifically as its just some library, honestly at this point I'm probably not going to use it because it's too unwieldy and over engineered.

But here is what I have had to do so far to even try to get things compiling. I feel that Mir trying to be @safe is forcing me to write even less safe code than I would normally just to try and get it through the type system. At a certain point you just start casting and annotating randomly just to get it to compile. These sorts of workarounds seem to be what always happens when I encounter these annotations. In my own code I avoid them like the plague if I can't get by without them.


struct PageSerial {
	// this is just some GUI element
	// the contents of this gui element is what I am trying to ser/deser
	private Grid g;

	@serdeKeys("blocks")
	@serdeIgnoreIn
	//@serdeLikeList
	auto blocks_out() @property @trusted const {
		// Had to put @trusted because the mir function to ser/deser is @safe
		// Had to put const because the mir function decides to cast my type to const when working with it?? no idea why

		import std.algorithm : filter, map;
		import std.range : ElementType;
		// have to cast away the const
		auto t = cast(PageSerial*)(&this);
		auto r =
			(t.g).contents.children()[]
			.filter!(c => (cast(GridBlock)c !is null))()
			.map!(c => GridBlockSerial(cast(GridBlock)c))();
		static assert(is(ElementType!(typeof(r)) == GridBlockSerial));

		// trustRange wraps all the range functions with @trusted
		// had to do this because somehow the map type's empty evaluated to @system
		// but the range is iterated in the @safe serializeValue so i have to force it
		return trustRange(r);
	}


	// this one doesnt even work so not even sure if its set up right for Mir
	// at least it compiles but trying to read the data back in causes exceptions
	@serdeKeys("blocks")
	@serdeIgnoreOut
	@serdeLikeList
	@serdeProxy!GridBlockSerial
	auto blocks_in() @property @trusted const  {
		struct R{
			private Grid x;
			void put(GridBlockSerial b){ x.addElement(b.makeElement()); }
		}
		return R(cast(Grid)g);
	}
}

GridBlockSerial is another serialized type but not as complex. The PageSerial type gets passed into Mir's serializeJson and deserializeJson. Each of which pass them down into some lower level templates (eg Mir's serializeValue) which is annotated @safe.

I try to go in and read Mir's code but its just a spaghetti mess of templates, its very hard to reason about and understand what is going on. It's like a ton of engineering effort was spent in an attempt to make the library "safer" in some type system sense, but the net result is a library that is borderline unusably cumbersome and actually incentivizes me to write less safe code.

If the argument is that the library should have been designed to infer the annotations, then why even have the annotations in the language at all if the best practice is to infer them.
January 19
On Thursday, 19 January 2023 at 04:06:27 UTC, A moo person wrote:
> If the argument is that the library should have been designed to infer the annotations, then why even have the annotations in the language at all if the best practice is to infer them.

In principle, the annotations are there mainly for (a) cases where attributes can't be inferred (e.g., extern functions), and (b) cases where you want to override attribute inference (e.g., @trusted).

Unfortunately, the compiler does not do as much inference as it could (e.g., it does not do inference for most ordinary non-template functions), which leads some D programmers to adopt the habit of sprinkling 'pure @safe nothrow @nogc' (or similar) all over their code as a precautionary measure. It seems like this is more or less what the Mir developers have done.

This is, ultimately, a usability problem with the language--sometimes it's best practice to write out the attributes by hand, other times it's a code smell, and if a library author doesn't know which is which, their users will suffer for it.

In the long run, I hope that we can address this by allowing the compiler to infer attributes for more functions; in the short run, the best we can do is try to fix the affected libraries.
January 19

On Thursday, 19 January 2023 at 01:51:41 UTC, A moo person wrote:

>

This is a semi rant born from frustration. Currently I'm trying to figure out Mir Ion so I can serialize some data, and oh boy is it frustrating.

[snip]

Have you tried filing an issue?

Some mir functionality could have better documentation or tutorials. I don't know a lot about mir ion, but that was my sense of it.

January 19
On Thursday, 19 January 2023 at 02:10:14 UTC, Walter Bright wrote:
> You can pretty much not use any attributes if you use none (including not using @safe).

If a function takes a delegate, there is no way (outside of changing the type, e.g., templating on the type of the delegate) to indicate conditional attributes.

So if someone in a library wrote

void process(void delegate() userData) @nogc {}

it is going to force userData to be nogc regardless of their own desires.
January 19

On 1/18/23 8:51 PM, A moo person wrote:

>

This is a semi rant born from frustration. Currently I'm trying to figure out Mir Ion so I can serialize some data, and oh boy is it frustrating.

The entire library is annotated out the wazoo with @safe and @nogc and pure and inout. Its a serialization library so as you might imagine it's doing reflection on types, calling member functions to try and serialize stuff, things like that. I have spent hours now adding attributes and casting attributes away. Writing @trusted wrapper types for std.algo ranges. Trying to decipher the vomit that dmd gives when one the types is not quite right. All I want to do is serialize a list of structs from a range and be able to deserialize them back but I can't do that because I still have not gotten it working.

It's actually maddening, I have spent 2 days on this. What value are all these attributes adding? Because it's a useability nightmare. Every time I encounter a D library that tries to be clever with these annotations, it's just absolutely made things more complex and less useable. Same story every time I have ever tried to use them in my own code.

Something like serialization/deserialization (which requires templates) should use attribute inference, and use unittests to prove memory safety/nogc/etc.

Now, this can break down if not explicit since the compiler sometimes gives up on inference, so maybe that's what happened here.

-Steve

January 19
On Thu, Jan 19, 2023 at 01:51:41AM +0000, A moo person via Digitalmars-d wrote: [...]
> The entire library is annotated out the wazoo with @safe and @nogc and pure and inout. Its a serialization library so as you might imagine it's doing reflection on types, calling member functions to try and serialize stuff, things like that.

IMO, library code, esp. templated library code, should never have any explicit attributes.  It should be left to compiler inference so that it will work with user types of any kind, and attributes within the library code itself should be enforced by appropriately attributed unittests.


> I have spent hours now adding attributes and casting attributes away. Writing @trusted wrapper types for std.algo ranges.

Something has gone deeply wrong when one has to resort to casts.  I'd say file a bug against the library.


> Trying to decipher the vomit that dmd gives when one the types is not quite right.  All I want to do is serialize a list of structs from a range and be able to deserialize them back but I can't do that because I still have not gotten it working.

I'd just write serialization code myself. Thanks to D's introspection abilities, this kind of code is not hard to write.  Use std.traits.FieldNameTuple to get a list of fields in the struct, then use std.conv.to to convert them to string.  On deserialization, do the same thing except use std.conv.to to convert from the string to the field type.  Probably some amount of customization will be needed if your fields need special handling (e.g. string literals that should be escaped in the serialized form / unescaped when you deserialize, etc.); you can just use `static if (is(typeof(field) == MyCustomType))` to handle those on a case-by-case basis.


> It's actually maddening, I have spent 2 days on this. What value are all these attributes adding? Because it's a useability nightmare. Every time I encounter a D library that tries to be clever with these annotations, it's just absolutely made things more complex and less useable. Same story every time I have ever tried to use them in my own code.

My policy is to let the compiler infer attributes as much as possible, and only write them manually when I really, really, *really* need to. Then use attributed unittests to make sure the compiler doesn't suddenly change its mind about how something is attributed.

In general, I think we should move in the direction of expanding attribute inference as far as possible.  Nobody wants to deal with attribute soup; it should be the compiler's job to automate this tedium as much as possible and only leave it up to the human when there's no other way around it.


T

-- 
Knowledge is that area of ignorance that we arrange and classify. -- Ambrose Bierce
January 19
On 1/19/2023 5:14 AM, Adam D Ruppe wrote:
> So if someone in a library wrote
> 
> void process(void delegate() userData) @nogc {}
> 
> it is going to force userData to be nogc regardless of their own desires.

If process calls userData, then that is as it should be. If userData is just being "passed through" and not called, then the easiest workaround is to cast it to something innocuous, or use a union.
« First   ‹ Prev
1 2 3 4 5