Rust Lang Team
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    --- title: "Design meeting 2025-02-05: Hierarchy of Sized traits part 2" tags: ["T-lang", "design-meeting", "minutes"] date: 2025-02-05 discussion: https://rust-lang.zulipchat.com/#narrow/channel/410673-t-lang.2Fmeetings/topic/Design.20meeting.202025-02-05 url: https://hackmd.io/VeTUAQpxRsqYcZWTjocI-Q --- We opened an RFC with what we discussed in our last design meeting on the topic and there has been lots of discussion on that. There have been many changes but most of them are small clarifications, there haven't been any major rewrites or very substantive changes in approach as there were with earlier drafts prior to our last design meeting. There's a summary comment on the RFC here describing all of the changes (and including the previous summary comment and its changes): https://github.com/rust-lang/rfcs/pull/3729#issuecomment-2517204789 They're split into big and small changes so you can skip the trivial ones. If you remember the RFC well and just want to catch up on the changes, then this link has a diff since our last meeting: <https://github.com/davidtwco/rfcs/compare/26f0aceabedf5bf17cd11ff26c829438e1659a5d...davidtwco:rfcs:sized-hierarchy?short_path=ff4bf3d#diff-ff4bf3ddea5d93dd09c3415aed6948be8be59f0a14127e03c654cde4aa1d91c2> Of course, feel free to re-read the whole document if you prefer. Here's what I'd like to get out of the meeting: - In our previous meeting, there appeared to be some consensus that this was a problem worth solving and that the solution proposed here was on the right track, I'd like to check that's still the case. - We've added a couple unresolved questions to the RFC. If any of these questions are ones that the team can quickly reach consensus on then I can update the RFC to incorporate that change. If any of those questions are more contentious then that's okay and we can leave it till we have more experience with the proposal. - I'd be interested in knowing what next steps should be: - If there are changes to be made or if the proposal is taking the wrong approach, then what changes should I be making to get closer to something the team would be happy with? - If the team is happy with the proposal, could we proceed with an experimental implementation*? Would accepting this RFC be blocked on the const traits RFC being accepted or could it be accepted subject to whatever solution for const traits ends up being? *subject to the people working on const traits being happy with me building atop it. As a point of information, I've started working on an implementation of this - it currently implements everything up to making the traits const and passes all the tests/builds stage two/etc. So I'm confident that an implementation is feasible and I haven't run into any unexpected backwards incompatibilities (and have had to perform many appropriate and backwards compatible relaxations of bounds in the standard library in that implementation, because extern types are used in std and in rustc today). Without further ado, the RFC: <https://github.com/davidtwco/rfcs/blob/0f9ec849ab44c73bd07b16768f7580568134aade/text/3729-sized-hierarchy.md> --- # Discussion ## Attendance - People: TC, scottmcm, davidtwco, yosh, nikomatsakis, Urgau, tmandry ## Meeting roles - Minutes, driver: TC ## Backwards compatibility nikomatsakis: Today this compiles too... ```rust const fn f<T: Clone + ?Sized>() { let _ = size_of::<T>(); } ``` As does: ```rust pub const fn f<T: Clone + ?Sized>() { let _ = const { size_of::<T>() }; } ``` ...so it seems as if the `Clone` trait would have to be changed to have `const Sized` as a supertrait bound, right? Isn't that a problem? davidtwco: This is a tricky case that I hadn't thought of. nikomatsakis: I believe we can perhaps finesse this with an edition transition and/or just investigate if it's an issue in practice; it needs to have the `?Sized` for example to be an issue. scottmcm: This example is not in a const position, so it only needs `~const Sized`, right? ## Hot takes on the unresolved questions > Which syntax should be used for opting out of a default bound with const traits and a trait hierarchy? > > This RFC is primarily written proposing the "positive bounds" approach, where introducing a positive bound for a supertrait of the default bound will remove the default bound. Alternatively, described in Adding ?MetaSized, existing relaxed bounds syntax could be used, where a desired bound is written as opting out of the next strictest. nikomatsakis: I feel strongly we should use the positive bound approach. I am like 99.9% fixed on this point. davidtwco: I agree, but the most contentious discussion on the RFC was on this point - some feel the `?` sigil indicating that an opt-out is happening is quite important. My local experimentation uses the alternative syntax just because it was slightly easier to implement but either should be possible. tmandry: Looking at the table, I agree with the positive bound approach. scottmcm: I agree with the phrasing of 'I don't want to think of the "next most precise thing"'. nikomatsakis: What would *persuade* me is to either (a) try the version I want in practice and be unhappy or (b) try it with some people and see them fail miserably. davidtwco: I've implemented enough of this to know it would be relatively straightforward to implement both syntaxes if we wanted to do this experiment. Implementing the `?Sized` variant also introduces the possibility of incoherent bounds, e.g. `?MetaSized + Sized`, that would need to be an error. With positive bounds, incoherent bounds aren't possible, you just always get the strictest one you wrote. TC: Maybe we could essentially do both. Perhaps we could support trait aliases like: ```rust! trait Foo = ?Sized; ``` TC: Niko's argument comes at it from a perspective focused on user experience. RalfJ is coming at this from a POV about language principleness. Maybe the principled thing to do is add the `?` thing and to provide the good user experience through trait aliases. Then people could click through in `rustdoc` to see what's actually going on. davidtwco: the language supports the above actually (well, `trait X = Y`, `T: ?X`), I use it to introduce this to the standard library without doing `cfg(bootstrap)` everywhere in my experimental implementation nikomatskais: I think Ralf is actually wrong on this and it's connected to the forwards compatibility issue. Under this meaning, everything desugars to traditional bounds, it's just that there is one you didn't write because it was automatic. But with `?`, you are getting "one level below" in the hierarchy, and that is actually a malleable thing (which is why `?Sized` doesn't work out). Imagine you have three levels of traits: ```rust trait Level3: Level2 {} trait Level2: Level1 {} trait Level1 {} fn foo<T: ?Level2>() { // This means... what exactly? It hurts my head to think about it. // I think it means `T: Level1`? // davidtwco: it does (thanks!--niko) } ``` And now you want to add `Level 1.5`, what happens? In particular, `foo::<L1>` used to work, but now .. it doesn't? But if I had written... ```rust fn foo<T: Level1>() {} // this function's meaning remains unchanged. ``` davidtwco: **action item** - express a bias towards positive bounds but that it will be considered later, not a blocker > Should std::ptr::Pointee be re-used instead of introducing a new marker trait? > > This would require an additional changes to avoid ambiguity, as described in Why not re-use str::ptr::Pointee?. nikomatsakis: I don't have a super strong opinion. I don't actually like the name `Pointee` very much, but I think we've doubled down on it with `CoercePointee`, right? Sad. In retrospect I wish I had suggested `CoercePointerTarget`. Anyway, DISAGREE AND COMMIT. It's fine. Reading the unresolved questions, it's all about ambiguity, but I think that's a bit odd. We have some RFCs that have been flying around for how to resolve ambiguous method calls involved sub/super trait hierarchies, right? I think I would expect associated types to work the same. My memory is that we've settled on the rule of "prefer the item from the subtrait", under the assumption that the subtrait would not have intentionally shadowed the supertrait, so it presumably pre-exists (which applies here too). davidtwco: I'm not sure it's about super/sub traits, because the associated item would likely be from a entirely distinct hierarchy. nikomatsakis: Pointee is the root of all hierarchies, it seems to me, given that `MetaSized` is an implicit supertrait. davidtwco: ~~is it? `T: Sized: MetaSized: Pointee` and `T: Foo` are distinct? `Foo::Metadata` and `Pointee::Metadata` overlap for `T::Pointee`, and aren't in a hierarchy~~ > What is the precedence for ?const Trait - (?const) Trait or ?(const Trait)? This isn't a question for this RFC to resolve but this RFC takes a conserative approach and always adds explicit parentheses. nikomatsakis: What the heck is `?const`. I do not want to see this syntax in Rust. Not really any syntax in which `?` appears in front of an identifier. davidtwco: Discussion on the RFC raised the possible ambiguity, does `?const Trait` opt-out of the const or the whole bound is the key question. > (added by nikomatsakis) `MetaSized` nikomatsakis: I do not like `MetaSized` and prefer `SizedVal` or `ValueSized`. davidtwco: **action item** - add unresolved question for naming of the traits ## Incremental rollout tmandry: What are the implications of stabilizing a little bit of the hierarchy at a time? Does it need to be done in a particular order? davidtwco: I don't think there are any complications with this - you can stabilise the new traits or the constness of any of the traits separately - that has the expected implications for bounds you can then write, but it wouldn't break anything. tmandry: Just to check, that also applies if we go beyond the current proposal, like something weaker than `Pointee`? davidtwco: I think so. I discuss the backwards compatibility of introducing new traits in the middle or after the proposed traits in one of the footnotes of the RFC. I think the "can they be stabilised independently" question is the same for those as for the proposed traits. davidtwco: Also beneficial for the positive bounds approach - to refer to a thing needs it to be stabilised, rather than the next strictest thing being stabilised. ## Compatibility with `AlignSized` / `size != stride` proposals tmandry: e.g. https://internals.rust-lang.org/t/pre-rfc-allow-array-stride-size/17933 davidtwco: I've read the existing RFCs for `Aligned`, which are discussed briefly in the future possibilities, but unfamiliar with this particular pre-RFC, will need to read up on it. scottmcm: Obligatory link to <https://lang-team.rust-lang.org/frequently-requested-changes.html#size--stride> where lang has made a fairly strong statement that we wouldn't do this, though not one that couldn't be overridden. tmandry: Well it would need to be another default bound like here. I would be curious to know how it fits and would like to know we aren't closing any doors. tmandry: Looking at the `Aligned` proposal I think it's different: The types in question have a known alignment, it's just that they have a "data size" that is not a multiple of that alignment. The proposal is essentially: - Add `AlignSized` as an implicit bound like `Sized` - Add `std::mem::data_size_of::<T>()` which is how much space its bytes take up in a struct - which equals `std::mem::size_of::<T>()` for all `AlignSized` types - for other types, it may be less, and not include padding bytes - An array `[T]` continues to use `std::mem::size_of::<T>()` (the stride) between bytes, while struct layouts including these types can be more compact Let's say we call the "positive" version of `T: ?AlignSized` `T: Compact`. Where does it go here? ``` ┌────────────────┐ ┌─────────────────────────────┐ │ const Sized │ ───────────────────────→ │ Sized │ │ {type, target} │ implies │ {type, target, runtime env} │ └────────────────┘ └─────────────────────────────┘ │ │ implies implies │ │ ↓ ↓ ┌──────────────────────────────┐ ┌───────────────────────────────────────────┐ │ const MetaSized │ ──────────→ │ MetaSized │ │ {type, target, ptr metadata} │ implies │ {type, target, ptr metadata, runtime env} │ └──────────────────────────────┘ └───────────────────────────────────────────┘ │ implies │ ┌───────────────────────────┘ ↓ ┌──────────────────┐ │ Pointee │ │ {runtime env, *} │ └──────────────────┘ ``` nikomatsakis: Would it be orthogonal? New implied bound is `EquiSized` and could relax to `Compact`. scottmcm: Don't think a `Compact` type could ever be `MetaSized`, in a world where that means the same as our `?Sized` bounds today, which unsafe code assumes. Could be a hierarchy that we build on top of `Pointee`. We could put it between `Pointee` and `MetaSized`. tmandry: We could add a new level like `MetaCompactSized` for unsized types that fit in. nikomatsakis: Don't think we're closing a door but it may not be as expressive as we want. scottmcm: Want to make sure that relaxing bounds is backwards compatible, no matter what. e.g. if we relax `Box<T: MetaSized>` to `Box<T: Compact>`. ## Way to frame this without const traits? TC: Const traits may be the right way to frame this, but I wonder whether this can be framed usefully without them in some way. If they could, that would help in decoupling this RFC from another rather hard problem, and might help it to make independent progress. TC: Perhaps we could always later do some kind of trait aliasing to map a later const trait version to whatever non-const traits we used to model this without that. davidtwco: I'm open to ideas but I believe that `Copy: Clone: Sized` means that the scalable vectors need to implement `Sized` to implement `Copy` and that can't be relaxed backwards compatibly - constness works around that. I explored doing this without const traits and didn't think it could be done. TC: Could you elaborate on the "constness works around that" bit? What is it specifically that helps here? davidtwco: Without constness, `Sized` means "known at compile time", because of `const fn size_of<T>` and that doesn't work for scalable vectors, but we can't relax `Clone: Sized`, so we need to make scalable vectors fit in `Sized`. Constness lets us do that; we are partitioning `Sized` while still being `Sized`. TC: Does this rely on the conditional `~const` bounds at all, or no? davidtwco: It benefits from them, `const fn size_of<T: ~const Sized>` allows this to accept scalable vectors at runtime, without the conditional bound that can't happen. But we could do `const fn size_of<T: const Sized>` and just disallow that too. The conditional bounds allow functions to be scalable-vector-agnostic, in other words, while still being const. TC: It seems to me the conditional bound must be the main thing, because aside from that, I would imagine we could just model this in terms of some other trait with appropriate super-/subtrait relationships or blanket impls. The special thing about the `const` trait work is that conditional bound. davidtwco: I explored a `RuntimeSized` trait below `Sized` and above `MetaSized` to do this initially (mentioned in footnote), but you can't relax `Clone: Sized` to `Clone: RuntimeSized` for scalable vectors to be `Copy`, it needs to be const. If it didn't have conditional bounds, it would still allow us to implement `Copy`, and prevent scalable vectors being passed anywhere that can be const with a `const Sized` bound, but it is limiting not having the conditional bounds, they serve a useful function. TC (post-meeting): This discussion continued [in a thread](https://rust-lang.zulipchat.com/#narrow/channel/410673-t-lang.2Fmeetings/topic/Design.20meeting.202025-02-05/near/498167369). Other than the conditional bounds, this can be modeled with normal traits, and David updated the RFC to describe this. nikomatsakis: I'm becoming less convinced that the default bound should be `const Sized`. It isn't what I think most code needs and it rules out compatibility with some types -- and it's the only "const" bound that you get by default. I feel like the more it aligns with "regular const" the less annoying it will be. But overall `const SizeOfVal` doesn't seem like a terrifying idea, it just says "I can call `size_of_val` at const-time". scottmcm: One counterpoint that comes to mind is inside the standard library... something with ZSTs. TC: I think of runtime `Sized` types like SVEs as us querying the processor to get the size. The RFC calls these something like "statically known at runtime". But I wonder whether this notion is extensible. We could instead, e.g., be querying a network API at startup to get the size of some type. tmandry: It means you can generate some code as if the size was statically known. TC: Let's talk through how this depends on const trait impls exactly. davidtwco: ```rust trait Foo: Clone { fn uses_supertrait_bound() { requires_sized::<Self>() } } fn requires_sized<T: Sized>() { /* ... */ } // if you changed trait Clone: Sized // to trait Clone: RuntimeSized // above no longer compiles impl Copy for svint8_t {} // necessary, implies svint8_t Sized //impl Sized for svint8_t // Does not do this. impl RuntimeSized for svint8_t {} impl Sized for svint8_t {} // does not: //impl const Sized for svint8_t trait ConstSized: Sized {} // do not: //impl ConstSized for svint8_t {} // svint8_t can't be Sized, Sized requires compile time fn do_stuff<T: Sized>(value: T) { size_of(value) } // T: const Sized // const fn size_of<T: Sized> --> const fn size_of<T: ConstSized> // const fn size_of<T: Sized> --> const fn size_of<T: const Sized> ``` (The meeting ended here.) --- ## Thoughts on the various `Pointee`s scottmcm: I'm reminded of [ACP: replace use of Pointee trait with a ptr::Metadata type #246](https://github.com/rust-lang/libs-team/issues/246) when I see the notes about `Pointee`. Would something like that be beneficial to the proposal? davidtwco: I think we'd be compatible with that. We avoid reusing `ptr::Pointee` because adding it as a supertrait of `Sized` means `T::Metadata` is ambigious, otherwise it represents approximately the same thing. If it was a type then we could still add a marker trait without the associated type like we're proposing now w/ whatever name we wanted. ## Thoughts on SVE tmandry: Okay to leave out of scope, but: How are we really gonna deal with SVE types? They can't go in structs or spill to the stack? davidtwco: my colleague at Arm has an RFC proposing `repr(scalable)`, it may address these points - https://github.com/rust-lang/rfcs/pull/3268 - but it needs this RFC as a foundation to avoid type system special-casing. I've mostly been focusing on this part so don't recall how we address those specific concerns. ## niko jots down notes nikomatsakis: I want to drop down notes here. I'll try to coallesce these into *questions* later, if needed. Aligned: I still think the "basic shape" of this RFC feels right to me. I like the use of `const` and I like moving away from `?Trait` towards "name the special trait that you want / need". Bikeshed: I do not like the name `MetaSized`. It is difficult for me to remember what it is. Basically any time people use the term "meta" I feel confused. I wonder what other names have been considered. To me the salient detail is it is something where the *type* does not have a size, but *values* of the type do -- so perhaps `SizedVal` or `SizedValue`? This also aligns with "you can call `size_of_val`". Observation: Writing `const Sized` does feel a *bit* verbose. I think it's probably ok but I could imagine introducing an alias like `trait StaticSized = const Sized`. Alternatively, perhaps we make `Sized` be that alias and create a new name (say, `SizedType`) and then change the definition of `trait Clone: Sized` to be `trait Clone: SizedType`. I think if we do that atomically it should be ok? (Hmm, is there an interaction around backwards compatibility here...?) *Question:* Today, I can write this on stable... ```rust const fn foo<T: Sized>() { let c = size_of::<T>(); } ``` ...but shouldn't it (per this RFC) require `T: const Sized`? Am I missing something? Oh, it's done through the magic of editions... "In the current edition, all existing Sized bounds will be syntatic sugar for const Sized (both explicitly written and implicit)." *Got it.* But today this compiles too... ```rust const fn foo<T: Clone + ?Sized>() { let c = size_of::<T>(); } ``` ...so it seems as if the `Clone` trait would have to be changed to have `const Sized` as a supertrait bound, right? Or else are we dealing with that in some way? On the interaction with `const` traits: I do not think we should block accepting the RFC on accepting const traits. I do think we should add an unresolved question to revisit the relationship between them, and I would of course be reluctant to *stabilize* without having an accepted const trait RFC. Question: I *think* that although "In practice, every type will implement Pointee", one thing I like about this proposal is that we can in fact change this later. We could add a new layer below Pointee (say, used to indicate logical values that reside on the GPU and hence do not have an address or something) -- all existing code would either have the default bound or would have explicitly written `T: Pointee`, so only new code that names `T: NewTraitName` (whatever that new trait name is) would be impacted. Of course we may find that some existing functions ought to have looser bounds, but I believe that is in principle achievable as well. *Question:* Supertrait bound: "It is necessary to introduce a implicit default bound of const MetaSized on a trait's Self type in order to maintain backwards compatibility?" Should we make this tied to the edition? Hmm. I'm of two minds. On the one hand, I rather like the idea that trait `Self` types are as general as they can be by default, but on the other hand, this undermines my statement that we can add a new "level" of types. *Question:* "This is forward compatible with trait bounds which have sizedness supertraits implying the removal of the default const Sized bound." -- it is?? I rather think that supertrait bounds *should* be special, but I don't want to tie this into this RFC, not sure how to think about that. I think to be truly compatible you'd have to require `T: Foo + MetaSized` be written explicitly.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully