3 ms·
Coming from C, I always see these "Self" variables/types as counter-intuitive, and contribute to those cases where a function requires a parameter which I don't
by scoutt 4y ago
Coming from C, I always see these "Self" variables/types as counter-intuitive, and contribute to those cases where a function requires a parameter which I don't have to supply (just for these functions accepting "Self"), which could be an exception instead of a rule. I would rather prefer the C++ "this" keyword usage.
Also, do "Self" kind-of variables occupy memory?
How can I declare a (packed) struct describing a payload in a way that the struct can be then sent "as is" as a network packet?
Let's say this struct has a "calculateChecksum()" method. Will I need to declare "Self"? If so, will be "Self" part of the struct's memory layout?
- wchar_t 4y agoNope, since Self is a declaration and not a field it won't take up memory.
- quietbritishjim 4y ago> Will I need to declare "Self"? If so, will be "Self" part of the struct's memory layout? I'm only familiar with C++, not Zig, but it doesn't seem that different to me. If a C++ class includes a typedef, then that doesn't contribute to the class's footprint. Even the explicit self parameter is not so different. In C++, a method can be marked as const or even && (rvalue reference) and that's applied to the implicit this parameter. If anything, it would be clearer if this was an explicit parameter; as of C++23, it actually is allowed to be [1], called "deducing this". [1] https://devblogs.microsoft.com/cppblog/cpp23-deducing-this/ https://devblogs.microsoft.com/cppblog/cpp23-deducing-this/
- flohofwoe 4y agoThere's nothing special about the 'Self' (it's just a type). In general, consts in Zig are purely compile time things and don't take up memory. And putting a function inside a struct which takes a pointer to its struct type as first argument just allows method-call-syntax-sugar, but you can also write it as regular function (which must then be namespaced with the struct type though): const Bla = struct { const Self = @This(); val: i32 = 0, fn add(self: *Self, val: i32) void { self.val += val; } }; pub fn main() void { var bla = Bla{}; // with method-call syntax sugar bla.add(2); // without method-call syntax sugar Bla.add(&bla, 3); } ...but in this case it's probably better to not use Self and @This() (it mostly makes sense with generics). const Bla = struct { val: i32 = 0, fn add(self: *Bla, val: i32) void { self.val += val; } };
- bobbylarrybobby 4y agoI think Rust does this quite well. There are a few handy rules. 1. If the first argument of a function is `self`, then that function is treated as a method and can be called like `obj.meth(args)`. Functions without `self` must be called statically, which in the case of an object would look like `Type::meth(obj, args)`. The second form is used occasionally by common wrapper types like Box and Rc to avoid dereferencing “piercing the veil” and calling a method on the wrapped object (Rust has no separate `obj->meth` syntax, which IMO is its own issue). 2. Having access to `Self` lets you specify the types of non-this arguments as well as return types, like `fn add(self, other: Self) -> Self {...}`. You can also use it as the name of the type when (de)structing objects, e.g., `let Self { fields } = other` or `return Self { fields }`. Writing out the name of the current type each time could get unwieldy. 3. `Self` as a namespace is handy when dealing with enums, e.g., `match self { Self::First => ..., Self::Second => ...}` or static functions like `Self::func(args)`. It's crucial in trait implementations when referring to associated types, e.g., in Iterator there's `type Item; fn next(&mut self) -> Option<Self::Item>;` I don't why a Self variable would be placed on the stack differently from non-Self variables; method calls have always just been syntactic sugar + namespacing for a free function, no?
- quietbritishjim 4y ago> method calls have always just been syntactic sugar + namespacing for a free function, no? Not quite; they're more than that for languages that have dynamic dispatch, like virtual methods in C++ or dyn impl in Rust. In that case, that argument affects which function is called. In the case of multiple inheritence (in C++), the implicit this parameter is also potentially subject to a pointer offset; if it's virtual multiple inheritence then that offset may even be computed at runtime.