Skip to content

Basic documentation for Struct Classes - #603

Open
JeffersGlass wants to merge 7 commits into
spylang:mainfrom
JeffersGlass:docs-struct-basics
Open

JeffersGlass wants to merge 7 commits into
spylang:mainfrom
JeffersGlass:docs-struct-basics

Conversation

@JeffersGlass

@JeffersGlass JeffersGlass commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Adds the beginnings of documentation for struct classes. (Documentation Preview Page)

I'm working on expansions to this - special methods like __convert_to/from__, __getattr__/__getattribute__, methods that are similar to CPython (__eq__, __add__ etc), which are more like CPython's data model documentation. But I wanted to get this out there before it got too big to conveniently review.

@JeffersGlass JeffersGlass added the documentation Improvements or additions to documentation label Jun 24, 2026
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Documentation Preview
Preview documentation for PR #603.
Last Updated: 2026-07-01 18:47 UTC

@antocuni antocuni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

One note: you might want to add two things:

  1. structs are passed BY VALUE, meaning that they are copied around, and thus big structs might have a penalty; gc_ref[S] exists, although at this time it's considered a low-level primitive and end users should not use it explicitly. Later, we will provide better absrtractions on top of this.
  2. you might want to document that if you raw_alloc/gc_alloc a struct, THEN you can mutate its content.

You might also want to mention the __ll__: gc_ptr[S] pattern, although this might be material for another PR.

Comment thread docs/src/howto/structs.md Outdated
@@ -0,0 +1,157 @@
title: Struct Classes

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd call them just "Structs".

The fact that we need a "class" statement is a necessary evil because of Python grammar.
If/when we will be ready to deviate more from CPython grammar, I'd like to be able to write this:

type MyMetaclass Foo:
    x: int
    y: int

and then Foo will be created by calling MyMetaclass (this is already true in ASTFrame.exec_ClassDef). In this scheme, class and struct will be just two builtin metaclasses, and class Foo would be kept as a syntax sugar for type class.

Anyway, that's the far future, but I think it shows why we shouldn't call them "struct classes"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense! I've changed struct class to just struct everywhere.

Comment thread docs/src/howto/structs.md Outdated
Comment thread docs/src/howto/structs.md Outdated
@JeffersGlass

Copy link
Copy Markdown
Contributor Author

I've addressed all the comments - thank you for them! I'm not super happy with my description of the __ll__ = gc_ptr[S] pattern here ... do you have any suggestions on the phrasing for why this pattern is useful?

@antocuni antocuni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!
I tried to write an expanded section about the "new type" idiom and the __ll__ idiom, I hope you like it.

Comment thread docs/src/howto/structs.md
print(p.name, "has", len(p.books), "books") # Alice has 0 books
```

By convention, if a struct is simply being used to wrap a lower-level type (to add constructors, methods, etc), that lower level type is denoted as `__ll__`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about something like this:

## "New type" pattern

A common idiom is to have a struct with a single field of type `T`: this creates a zero-overhead wrapper around `T`, which is a completely new type and makes it possible to enforce compile time type guarantees and/or to add methods and special methods to it.

The zero-overhead is guaranteed by the fact that structs are allocated "on the stack", passed by value, and translated into C structs by the C backend. 

This is similar and inspired by the [newtype idiom in rust](https://doc.rust-lang.org/rust-by-example/generics/new_types.html):

```python
@struct
class Kilometers:
    v: float

    def to_miles(self) -> Miles:
        return Miles(self.v * 1.609344)

@struct
class Miles:
    v: float

    def to_kilometers(self) -> Kilometers:
        return Kilometers(self.v * 1.609344)


def is_a_marathon(distance: Miles) -> bool:
    return distance.v >= 26.2

def main() -> None:
    distance = Miles(30.0)
    distance_km = distance.to_kilometers()
    print("Is a marathon?", is_a_marathon(distance))
    print("Is a marathon?", is_a_marathon(distance_km.to_miles()))
    # print("Is a marathon?", is_a_marathon(distance_km))  ## TypeError
```

## `__ll__` pattern

This is a special case of the "new type pattern".  It is used when you want to create a high-level type in terms of a low-level, possibly unsafe, implementation which is hidden to the end user.

Currently, `__ll__` is just a naming convention but in the future it might become special-cased by the compiler to make it truly private.

INSERT THE BookData example

Note that in this example, `BookData` is potentially a big struct, while `Book` is a wrapper around a *pointer* to it, so more efficient to pass around.
Normal `class`es will be (automatically) implemented using this idiom, but currently they are not supported and must be done manually.

This is also how many builtin types are implemented: `list[T]` is a pointer to `ListData[T]`, `str` is a pointer to `StrObject`, etc.  See `stdlib/*.spy` for more examples.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants