# a page
> Fleeting Moments, Lasting Impressions.
Public Ghost content for AI and LLM tooling. This file includes a bounded export of public pages first, then recent public posts.
Append `.md` to any post or page URL to get the content in Markdown (for example, `/example-post.md`).
## Pages
### お問い合わせ
URL: https://www.sakashita.page/contact/
Last updated: 2025-10-27T11:35:15.000Z
当サイトは個人的な興味や考えを綴っているブログです。記事の内容についてのご質問、ご感想、その他のお問い合わせがございましたら、お気軽にご連絡ください。
### 一般的なお問い合わせ
記事に関するご質問、ご感想、ご指摘、その他のご連絡は、以下のメールアドレスまでお送りください。
[Contact](mailto:hi@sakashita.email)
すべてのメールに目を通しておりますが、ご返信には数日から一週間程度のお時間をいただく場合がございます。個人で運営しているブログのため、即座のご返信が難しい場合もございますが、ご理解いただけますと幸いです。
なお、営業目的のご連絡はご遠慮いただいております。
---
### 著作権に関するお問い合わせ
当サイトのコンテンツが第三者の著作権を侵害している、または当サイトのコンテンツが無断で使用されているといった著作権に関するご連絡は、以下の専用アドレスまでお願いいたします。
[著作権侵害連絡先](mailto:dmca@sakashita.email)
著作権侵害のご申し立てをいただく際は、以下の情報を含めていただけますようお願いいたします。
- 侵害されているとお考えの著作物の特定に必要な情報
- 当サイト内の該当コンテンツのURL
- ご連絡先情報(メールアドレス等)
- 著作権者またはその代理人であることを示す情報
---
## セキュリティ研究者へ
当サイトのセキュリティに関する脆弱性を発見された場合は、責任ある開示にご協力いただけますと幸いです。[Security.txtを公開しております](https://sakashita.page/.well-known/security.txt)ので、そちらをご参照ください。
セキュリティに関するご連絡には、機密性の観点からPGPの使用を推奨しております。PGPを使用していないメールについては、セキュリティ上の理由から対応できない場合がございますので、あらかじめご了承ください。
PGP公開鍵のフィンガープリント:
```fingerprint
bb79ad70a1b1d5b02fa5cacb13471167127d9902
```
[publickeypublickey.asc673 Bytesdownload-circle](https://www.sakashita.page/content/files/2025/03/publickey.asc "Download")
### このサイトについて
URL: https://www.sakashita.page/about/
Last updated: 2026-06-11T13:07:47.000Z
当サイト「[**sakashita.page**](http://sakashita.page)」は、**坂下康信(さかした やすのぶ)** が運営する個人ブログです。
高知大学 人文社会科学部で哲学を学んでいます。2024年から約1年間、北京語言大学へ交換留学していました。
プログラミングは中学時代に C / Python から始め、いまは Java を中心に。Platform Engineering / Developer Experienceに関心があります。「問いの立て方」と「実装力」を行き来しながら、考えたこと・作ったものを気ままに綴っています。
## ざっくり経歴
- 高知大学 人文社会科学部 人文社会科学科(2022〜、2028年卒業見込み)
- 北京語言大学 交換留学(2024〜2025)
## インターン経験
- **ソニー 職場密着インターン(カメラ事業部 CI/CDチーム)**(2025年夏)— カメラ製品向けのビルド管理システムの改善に取り組み、git / Jenkins などを用いたCI/CDを実務で経験
- **富士フイルム「自然言語アカデミー(NLP)」**(2025年10月)— 自然言語処理の基礎を体系的に学習
- **富士フイルムビジネスイノベーション 技術系5日間インターンシップ**(2025年11月)— チームでWebアプリ開発に取り組み、AWSを用いたシステム構成を経験
- **ソニー・インタラクティブエンタテインメント(SIE)春インターンシップ**(2026年2月)— PlayStation®システムソフトウェア向けビルドシステム開発コース(3週間)。CI/CD環境の構築に取り組み成果を発表
## 資格
HSK 6級 / HSKK 上級 / Oracle Certified Java Programmer, Silver SE 11 / AWS Certified Cloud Practitioner
### プライバシーポリシー
URL: https://www.sakashita.page/privacy-policy/
Last updated: 2025-05-28T02:05:33.000Z
**当サイト運営者Sakashita(以下、「当方」といいます)は、個人情報を適切に取り扱うことを重要と考え、以下のとおりプライバシーポリシー(以下、「本ポリシー」といいます)を定め、皆様の個人情報を保護します。**
本ポリシーは、当方が運営するウェブサイト(以下、「当サイト」といいます)において適用されます。
### 1.法令等の遵守
当方は、個人情報の取り扱いに関して、「個人情報の保護に関する法律」(以下、「個人情報保護法」といいます)をはじめとする関連法令、および本ポリシーに定める内容を遵守します。また、法令・ガイドラインの改正や社会情勢の変化に応じて、適宜本ポリシーの見直しや改善に努めます。
### 2.個人情報の定義
本ポリシーにおいて「個人情報」とは、個人情報保護法第2条第1項に定める「生存する個人に関する情報」を指し、氏名、メールアドレス、その他の記述等によって特定の個人を識別できる情報をいいます。
### 3.個人情報の収集方法および利用目的
当サイトでは、以下の場合に限り、必要な範囲で個人情報を収集することがあります。
1. **お問い合わせ(InstagramやEメール等)**
お問い合わせ時に取得したお名前やメールアドレス等の個人情報は、お問い合わせへの回答および必要なご連絡のためにのみ利用し、それ以外の目的では利用いたしません。
2. **その他の任意提供**
当サイトが実施するアンケートやキャンペーン等において、ユーザーの任意により個人情報を提供いただく場合があります。その際は、取得の都度、利用目的を明示します。
当方は、取得した個人情報をあらかじめ明示した目的以外で利用することはありません。また、利用目的達成後には速やかに削除または適切な方法で保管いたします。
### 4.個人情報の管理
当方は、個人情報の漏えい、滅失、改ざんおよび不正アクセス等を防止するために合理的な安全対策を講じ、これを適切に管理いたします。万が一事故が発生した場合には、被害の拡大を防止するとともに、必要な情報提供や再発防止策を講じます。
### 5.個人情報の第三者提供
当方は、次の場合を除き、事前にご本人の同意を得ることなく、第三者に個人情報を提供しません。
1. 法令に基づく場合
2. 人の生命、身体または財産の保護のために必要で、本人の同意を得ることが困難な場合
3. 公衆衛生の向上または児童の健全な育成の推進のために特に必要がある場合で、本人の同意を取ることが困難な場合
4. 国または地方公共団体等が法令で定める事務を遂行することに協力する必要がある場合
5. その他個人情報保護法により提供が認められる場合
### 6.個人情報の開示・訂正・利用停止等
ご本人より、当方が保有する個人情報について開示、訂正、利用停止、消去、第三者提供の停止などのご要望があった場合、個人情報保護法および関連法令に基づき、合理的な期間および範囲で対応いたします。ご希望の際は、本ポリシー末尾に記載の**お問い合わせ窓口**よりご連絡ください。
### 7.Cookie(クッキー)について
当サイトでは**第三者 Cookie を利用していません**。ただし、以下のとおり、第三者の広告配信サービス等により Cookie が用いられる場合があります。
1. **Cookie のオプトアウト(無効化)**
Cookie の受け取りを拒否したい場合は、お使いのブラウザの設定により Cookie を無効にすることが可能です。ただし、Cookie を無効化すると、一部のサービスや機能が正しく利用できなくなる場合があります。
### 8.アクセス解析について
当サイトでは、アクセス解析ツールとして **Simple Analytics**(フリープラン)を利用しています。Simple Analytics はクッキーを使用せず、個人を特定できる情報を収集しないサービスです。
- **フリープランのためアクセスデータを公開**
フリープラン利用に伴い、当サイトのアクセス解析データは一般公開されています。詳細はサイト上の案内リンクよりご確認ください。
- **利用目的**
Simple Analytics を通じて取得される訪問ページ、訪問回数などの情報は、当サイトの運営・改善の参考としてのみ利用し、それ以外の目的で利用することはありません。
- [当サイトのアクセス解析ページ](https://dashboard.simpleanalytics.com/sakashita.page)
Loading chart...
[Simple Analytics - The privacy-first Google Analytics alternativeThe public stats of sakashita.page offered by Simple Analytics. The privacy-first analytics tool that doesn’t use cookies.Simple Analytics](https://dashboard.simpleanalytics.com/sakashita.page)
### 9.CDN(Cloudflare)の利用
当サイトは表示速度の向上やセキュリティ確保のため、**Cloudflare** を利用する場合があります。Cloudflare では、アクセスに関するログ(IP アドレスなど)を収集することがありますが、これらの情報は個人を特定する目的では使用されません。
- 取得されたデータは Cloudflare のプライバシーポリシーに従って管理されます。
- 当サイトでは、この情報をサービス運営・改善のためにのみ利用し、法令に基づく場合を除き、第三者に開示・提供することはありません。
[Cloudflare’s Privacy PolicyRead about Cloudflare’s privacy policy, which outlines general policy practices and more.Cloudflare](https://www.cloudflare.com/ja-jp/privacypolicy/)
### 10.著作権・肖像権
当サイトで掲載している文章や写真などのコンテンツの著作権は、当方(**sakashita**)に帰属します(一部引用等を除く)。無断での転載・使用はご遠慮ください。
また、当サイトは著作権や肖像権の侵害を目的とするものではありません。掲載している写真は原則として当方が撮影・作成したものです。万が一、掲載内容に問題がある場合は、速やかに修正・削除等の対応をさせていただきますので、お問い合わせ窓口よりご連絡ください。
### 11.免責事項
1. **外部リンク先のコンテンツ**
当サイトからリンクまたはバナー等によって他のサイトに移動した場合、移動先で提供される情報やサービス等について当方は責任を負いません。
2. **掲載情報の正確性**
当サイトでは、正確かつ最新の情報を掲載するよう努力しておりますが、誤情報が入り込んだり、情報が古くなっている場合があります。当サイトの情報を利用することで生じたいかなる損害についても、当方は一切の責任を負いかねます。
3. **損害等の責任**
当サイトのご利用やアクセスにより利用者に生じたいかなる結果・損害についても、当方は責任を負いません。各自の判断と責任においてご利用ください。
### 12.プライバシーポリシーの変更
当方は、社会情勢の変化、技術の進歩、関連法令の改正等に伴い、予告なく本ポリシーを変更することがあります。重要な変更がある場合は、当サイト上でお知らせいたします。
### 13.お問い合わせ窓口
本ポリシーに関するお問い合わせや、個人情報の開示・訂正・利用停止等のお申し出につきましては、以下のcontactからご連絡ください。
[お問い合わせ当サイトへのお問い合わせは、主にメールおよび各種SNSのダイレクトメッセージ(DM)で承っております。運営者の Instagram アカウントは @yasunobu.sakashita ですので、お気軽にメッセージをお送りください。 Login • InstagramWelcome back to Instagram. Sign in to check out what your friends, family & interests have been capturing & sharing around the world. また、補助的な連絡手段として Eメールもご利用いただけます。 Contact ただし、メールは頻繁に確認しておりませんので、ご返信が遅れる場合がございます。お急ぎの場合や確実に連絡を取りたい場合は、Instagram の DM からご連絡いただけますと幸いです。 セキュリティ研究者へ いつもありがとうございます。 本サイトではSecurity.txtを公開しております。どうぞご利用ください。SakashitaのブログYasunobu](https://sakashita.page/contact)
---
**最終更新日:2025年3月14日**
**運営者:sakashita(個人)**
## Posts
### Lean 4.35詳解——vcgen、kernelによる再検査、機械整数を扱うgrind
URL: https://www.sakashita.page/lean-4-35-release-notes-and-analysis/
Last updated: 2026-10-25T13:59:59.000Z
Lean 4.35.0は2026年10月12日に公開されました。 2026年10月24日時点では、次の系列の4.36.0-rc1も10月14日に公開されていますが、以下は4.35.0の変更を扱います。 状態を更新するprogramの検証には`vcgen`、projectの証明を再検査する入口には`lake check`と`lake comparator`が加わりました([4.35.0の変更履歴](https://lean-lang.org/doc/reference/latest/releases/v4.35.0/)、[公開版の一覧](https://lean-lang.org/doc/reference/latest/releases/))。
`grind`は、`UInt8`や`BitVec`などの演算を、`Nat`や`Int`の演算へ写して扱う範囲を広げています。 `Decidable`の定義は、true側かfalse側の証明を持つinductive typeから、`Bool`と対応する証明を持つstructureへ変わりました。 コード例の対象はLean 4.35.0、Linux/x86\_64です。
## `vcgen`で、状態を更新するprogramの条件を証明する
`vcgen`は、programを含む証明目標を、成立に十分なverification conditionへ分解します。 たとえば現在の状態を読み、1を加えて保存し、変更前の値を返すprogramを考えます。 入力状態が`n`なら、返り値は`n`、終了状態は`n + 1`である、という条件を表せます。 状態を保存した結果だけでなく、返り値と終了状態の関係を同時に検証します([vcgenの仕様](https://lean-lang.org/doc/reference/latest/The--vcgen--tactic/))。
```lean
import Std
import Std.WP
import Std.Tactic.Do
open Std.WP Lean.Order
set_option experimental.vcgen true
def bump : StateM Nat Nat := do
let previous ← get
set (previous + 1)
return previous
theorem bump_spec (n : Nat) :
⦃fun s => ⌜s = n⌝⦄ bump
⦃fun result s => ⌜result = n ∧ s = n + 1⌝⦄ := by
vcgen [bump] with finish
```
`StateM Nat Nat`の最初の`Nat`は状態、最後の`Nat`は返り値の型です。 `get`は現在の状態を読み、`set`は新しい状態を保存します。 返すのは更新後の状態ではなく、`previous`へ保存した更新前の値です。 postconditionの`result = n`と`s = n + 1`は、異なる2個の値へ条件を付けています。
`⦃P⦄ program ⦃Q⦄`は、preconditionとpostconditionでprogramを囲むHoare tripleです。 `bump_spec`のpreconditionは、実行前の状態`s`が任意の自然数`n`に等しいという条件です。 postconditionは、返り値と実行後の状態を受け取ります。 `n`へ具体的な7や100を代入する有限の実行例だけではなく、宣言で量化した`n`を対象に証明しています。
`vcgen [bump]`は、`bump`の定義を展開して、`get`、`set`、`return`の仕様へ分けます。 `with finish`は、分解後に残る自然数の等式や連言を証明します。 依存するaxiomは、theoremの名前を指定して取得できます。
```lean
#print axioms bump_spec
-- => 'bump_spec' depends on axioms: [propext, Classical.choice, Quot.sound]
```
### 値を実行する代わりに、条件を後ろから変換する
programの通常の実行は、入力状態を出力状態へ変えます。 weakest preconditionの解釈は、実行後に必要な条件から、実行前に十分な条件を求めます。 たとえば`set (previous + 1)`の後で状態が`n + 1`なら、保存する式`previous + 1`が`n + 1`になる条件へ戻せます。 先行する`get`の仕様と組み合わせれば、元の状態が`n`というpreconditionへ接続します([predicate transformerの定義](https://lean-lang.org/doc/reference/latest/The--vcgen--tactic/Predicate-Transformers/))。
`return previous`の段階では、postconditionの返り値へ`previous`を代入します。 返り値の条件は`previous = n`です。 保存する状態への条件と、更新前に読んだ値への条件を合わせれば、両方の結論を導けます。 programを1個の不透明な呼び出しとして残すのではなく、operationごとの仕様を通じて証明目標へ移します。
verification conditionは、最終的に通常のLeanの証明目標です。 `vcgen`がprogramを分解しただけで、任意の条件を自動で真にするわけではありません。 たとえば保存する値が`previous + 1`なのに、終了状態を`n + 2`と宣言すれば、残る算術条件は成立しません。 分解する仕組みと、得た条件を証明するsolverを、`with`の構文で接続しています。
### monad以外のprogramにも解釈を与えられる
4.35の`vcgen`は、従来の`mvcgen`より、扱うprogramの型を一般化しています。 `Std.WP`の`WP` instanceでweakest preconditionの解釈を持てば、monadだけに限定されません。 独自の命令を表すinductive typeや、assembler、WebAssemblyのようなdeep embeddingも、解釈を定義する対象になります。 命令列を表すdataに対し、操作的な意味から条件を変換する規則を与える構成です([4.35のvcgenの採用範囲](https://lean-lang.org/doc/reference/latest/releases/v4.35.0/))。
同じ文字列をassemblerとして読んだだけで、任意のmachine上の実行まで検証できる意味ではありません。 `WP` instanceが解釈するprogramの型と、命令の意味が、対象となる実装を決めます。 具体的な命令をmodelへ対応させたうえで、preconditionからpostconditionへの含意を証明します。 `bump`のmodelは自然数の状態と`get`、`set`で構成し、外部のfile systemやnetworkを含めません。
assertionの型も、常に単純な状態から`Prop`へのfunctionに限りません。 `Std.WP`はcomplete latticeを基礎にし、含意、連言、選択などをlatticeのoperationとして扱います。 確率的なlogicやseparation logicのようなassertionへ、programの解釈を接続できる範囲を持ちます。 具体的なsolverが扱うassertionの性質と、programを分解する共通の仕組みを分ける一般化です。
`⌜P⌝`は、通常の`Prop`をassertion側へ埋め込む記法です。 `bump_spec`では、状態から`Prop`へのpredicateを使い、通常の等式をassertionとして記述しています。 assertionの型が抽象的な場合も、純粋な命題を同じ形式で扱えます。 precondition中の純粋な条件は、verification conditionを解く側の仮定へ移して使えます。
## loopのinvariantと、終了するためのmeasureをsourceへ書く
4.35では、`while`や`repeat`へinvariantとtermination measureを付ける構文が加わっています。 invariantはloopの開始時と反復後に維持する条件です。 measureは、継続する反復ごとに小さくなる値を指定します。 状態の関係を保つ証明と、反復が終わる根拠を、別のannotationへ表します([loopの契約に関するPR #14703](https://github.com/leanprover/lean4/pull/14703))。
```lean
set_option experimental.intrinsic true
def countDown (n : Nat) : Id Nat
ensures result => result = 0 := do
erased initial := n
let mut i := n
while i > 0
invariant exit => i ≤ initial ∧ (if exit then i = 0 else True)
decreasing i
do
i := i - 1
return i
```
`countDown`は、公式例に検証用の初期値と上限の条件を加えた形です。 入力7に対する返り値は、`#eval`で表示できます。
```lean
#eval countDown 7 -- => 0
```
`ensures`は返り値が0である条件です。 loopの終了時に`i = 0`が成立し、最後に`i`を返す処理と接続しています。
`decreasing i`は、単に`i`が0以上という条件ではありません。 loopを継続する場合に、次の反復でmeasureが減る根拠を要求します。 `i > 0`のbranchで`i - 1`へ更新するため、自然数の切り捨て減算でも、measureを減らせます。 0で同じ減算を繰り返す場合とは、loopを継続する条件が異なります。
invariantの`exit`は、loopの終了側か、継続側かを区別するbooleanです。 終了側では`i = 0`を要求し、継続側では当該部分を`True`としています。 上限の`i ≤ initial`は、両方へ共通する条件です。 4.35の`WhileInvariant`は、exit flagとloop stateを引数に取る専用の型です。 従来の直和を使う表現を置き換えています([WhileInvariantのPR #14845](https://github.com/leanprover/lean4/pull/14845))。
### `erased`の変数は、実行する状態を増やさず検証へ残す
`erased initial := n`は、検証に使う初期値を記憶しています。 反復後の`i`だけでは、元の入力との関係を比較できないため、invariantが参照する名前を残しています。 4.35の`erased` stateは、検証上は値を持ち、compileしたcodeでは実際の値の代わりにdummyを保持します。 履歴や初期値を記述するために、実行時のprogramへ同じdataを保存し続ける構成を要求しません([erased stateのPR #15090](https://github.com/leanprover/lean4/pull/15090))。
構文には、通常の束縛、変更可能な束縛、actionから得る値の形があります。 `erased x := e`、`erased mut x := e`、`erased x ← act`です。 invariantやassertionは検証上の値を参照できます。 消去した変数の値を、実行用の返り値へ自由に持ち出せる構文ではありません。
`requires`、`ensures`、`assert`、loopの`invariant`や`decreasing`は、intrinsicな検証構文としてexperimentalです。 `countDown`には、`experimental.intrinsic`と`experimental.vcgen`が必要です。
### 呼び出し先が変更しない状態をframeとして保持する
呼び出すfunctionの仕様が、状態の一部だけを記述する場合があります。 たとえば状態が自然数のpairで、functionが先頭の値を返して1増やす仕様なら、末尾の値への条件はpostconditionへ現れない場合があります。 呼び出し元が末尾は7と知っていても、仕様の適用後に同じ情報を自動で保持できるとは限りません。 `vcgen frames`は、呼び出しの前後で保持するassertionを指定する入口です。
frameに末尾が7という条件を指定すると、`vcgen`は条件を維持する証明も要求します。 呼び出すfunctionが実際に末尾を書き換える場合まで、指定だけで元の値を保ったと扱いません。 仕様が触れる部分と、保持したい状態の部分を、両方の条件へ接続します。 complete lattice上の一般化したframe preservationが、変更しないassertionを呼び出し越しへ運ぶ根拠になります([frameの構文と条件](https://lean-lang.org/doc/reference/latest/The--vcgen--tactic/))。
`mvcgen`と`mvcgen?`はdeprecatedになり、`vcgen`への移行対象です。 ただし`vcgen invariants?`によるinvariantの自動提案は、移植されていません。 4.35は提案機能が利用できないwarningを出し、keywordの削除も予定しています。 古いtactic名だけを置き換えた場合でも、利用していた補助機能まで同じ実装へ移るとは限りません([移行とinvariant提案のPR #14870](https://github.com/leanprover/lean4/pull/14870))。
## `lake check`は、生成した証明を別processのkernelへ渡す
Leanのtacticは、証明に相当するtermを作ります。 kernelは、termが宣言した型を持つかを検査します。 4.35の`lake check`は、projectの既定targetをbuildし、結果をexportし、別processでkernelへ通す入口です。 同時に、使用するaxiomが許可された集合に含まれるかを検査します([lake checkのPR #14990](https://github.com/leanprover/lean4/pull/14990))。
既定で許可する名前は、`propext`、`Classical.choice`、`Quot.sound`です。 当該3名以外のaxiomへ依存する宣言は、既定の判定では拒否します。
検査対象のprojectも、判定を返すprocessへ直接読み込むdataとは扱いません。 Lakeの設定はcodeとして評価でき、buildやelaborationでも処理を実行できるためです。 4.35は、projectの設定評価、build、exportを`bubblewrap`のsandbox内へ置きます。 判定を報告するprocessには、projectが作った`.olean`を直接loadしない構成です([4.35.0のCheck.lean](https://github.com/leanprover/lean4/blob/v4.35.0/src/lake/Lake/CLI/Check.lean))。
sandboxは、利用者のhome directoryを隠し、書き込み先を限定します。 dependency解決の段階だけnetworkを許可し、証明のexportとkernelの実行は別の権限で扱います。 build中に外部へ接続できた事実から、検査対象のcodeがkernelの実行環境まで同じ権限で変更できる構成にはしません。 動作にはLinux namespaceが必要で、4.35の当該sandboxはLinuxを対象にしています。
### `--paranoid`で、同梱の複数kernelへ通す
4.35の配布toolchainには、追加のcheckerが同梱されています。 `leanchecker-paranoid`、`lean4lean`、`nanoda`、`con-leche`、`con-ron`です。 `lake check --paranoid`は、通常のLean kernelに加え、同梱したcheckerを実行します。 1個だけ受け付けた結果を採用するのではなく、選んだcheckerがすべて受け付ける条件です([paranoidな検査のPR #15145](https://github.com/leanprover/lean4/pull/15145))。
`leanchecker-paranoid`は、memory allocatorを強化したLeanのcheckerです。 ほかのcheckerには、kernelの実装が異なるものも含まれます。 同じ証明termを別の型検査実装へ渡し、単一の実装だけに存在する不具合を検出する構成です。 すべてのcheckerが同じruntimeを使わないことと、すべてのcheckerに共有するdependencyが一切ないことは、同一の保証ではありません。
たとえば4.35のreference countの修正では、Lean runtimeを使う経路と、使わないcheckerの影響範囲が分かれています。 kernelの論理規則が異なるかという観点に加え、runtimeやallocatorを共有するかも、不具合の対象を決める条件になります。 追加checkerを同梱する変更は、実装の異なる検査を利用者が個別installして接続する作業を減らします。 必要な検査対象とcheckerを、同じtoolchainの版へ固定して扱えます。
### export済みのfileを再検査する
`lake check --from-export`は、既存のNDJSON exportを受け取ります。 別machineやvirtual machineで作った証明を、再buildせずに検査する入口です。 exportを作ったsourceとtoolchainの対応は、生成したartifactへ結び付けて保持する情報になります([既存exportを受け取るPR #15157](https://github.com/leanprover/lean4/pull/15157))。
`leanchecker`自身にも、`--from-export file.ndjson`が加わりました。 状態を更新する`bump_spec`、machine integerの境界を扱う証明、modular power、booleanの等式など、8宣言をexportするcommandは次の形です。 `ReleaseExamples`は、8宣言をまとめたmodule名です。
```sh
lake env /opt/lean/bin/leanexport ReleaseExamples -- \
boundedIncrement boundedDouble modularPower decisionAnd decisionOr \
bump_spec rewritten explicitConstructor > proofs.ndjson
/opt/lean/bin/leanchecker --from-export proofs.ndjson
# => Lean default kernel accepts the solution
/opt/lean/bin/lean4lean --import proofs.ndjson
# => checked 2222 declarations
```
`/opt/lean`は4.35.0を展開したtoolchainのpathです。 `lean4lean`の2222宣言には、指定した8宣言から参照する定義やtheoremも含まれます。 独立したcheckerによる再検査は、同じexportへ別の型検査実装を適用する処理です。

programと契約から証明termを作り、NDJSONへexportして2個のkernelへ渡す図
exportの先頭には、formatのversion、exporterのversion、Leanのversionとcommitが記録されます。 Lean 4.35.0に対応するexportのcommitは、`eb6cafb7290e8bd5f2937522d8d804f58f02bafc`です。 4.35のLakeも、exporterをtoolchainに同梱したものへ固定し、`.olean`を作ったcompilerとの対応を維持します。
### `lake comparator`は、証明した命題も比較する
`lake check`は、projectのtheoremがkernelで受け付けられるかを検査します。 `lake comparator`は、challengeの命題とsolutionのtheoremを対応させ、同じstatementを証明しているかも比較します。 solutionにある簡単な別の命題の証明を、challengeへ回答した証明として採用する条件を除きます。 設定には、challenge module、solution module、対象のtheorem名、許可するaxiomを指定します([lake comparatorのPR #14885](https://github.com/leanprover/lean4/pull/14885))。
たとえば要求したstatementが、任意の自然数`n`について`n ≤ n + 1`だとします。 solutionの命題を`n = n`へ置き換えれば、正しい証明termでも、要求されたstatementとは一致しません。 comparatorは、指定したtheoremのstatementと、必要な定義の対応を検査します。 証明探索が終了した結果と、依頼した命題に合う結果を、異なる判定として扱うcommandです。
`--challenge-from-export`と`--solution-from-export`は、両側へ既存のexportを渡す入口です。 sourceのmoduleをbuildする場合と、すでに生成した証明dataを比較する場合で、入力artifactが異なります。 既定の設定fileは`comparator.json`です。 `--paranoid`を使う場合には、solutionの証明へ追加のcheckerも適用します。
## `grind`は、machine integerを自然数や整数へ写す
`UInt8`の足し算は、無制限の自然数の足し算とは異なります。 255へ1を加えると8 bitの範囲を越え、結果は0です。 4.35の`grind`は、`UInt8`、`BitVec`、`Fin`などの演算を、専用solverが扱う`Nat`や`Int`の式へ写す仕組みを強化しました。 overflowと値の範囲を、変換する規則の中へ含めます([homomorphismのPR #14727](https://github.com/leanprover/lean4/pull/14727))。
```lean
theorem boundedIncrement (x : UInt8) (h : x.toNat < 100) :
(x + 1).toNat = x.toNat + 1 := by
grind
```
`boundedIncrement`の仮定は、`UInt8`の値を100未満へ制限します。 値を1増やしても256へ達しないため、`UInt8`側の加算結果を自然数へ移した値は、自然数側で1を加えた結果と一致します。 仮定の範囲と、machine側の演算の意味が、自然数側の等式を成立させています。
### 範囲の仮定を捨てると、255が反例になる
値を255へ固定すると、同じ形の等式は成立しません。 左辺は`UInt8`で1を加えてから自然数へ移すので0です。 右辺は先に自然数へ移して1を加えるため256です。
```lean
example : ((255 : UInt8) + 1).toNat = (255 : UInt8).toNat + 1 := by
decide
-- error: Tactic `decide` proved that the proposition is false
```
machine integerを自然数へ写せることは、すべての演算を無条件で通常の整数演算へ置き換える許可ではありません。 加算や乗算には、modulusや値域に対応する条件が伴います。 `boundedIncrement`では、入力の上限によってwraparoundを避けます。 単にcastの記法を消し、元の型が持つ有限幅を忘れた式を証明しているわけではありません。
`BitVec 8`でも、値を10未満へ制限すれば、2倍した結果が20未満であると証明できます。
```lean
theorem boundedDouble (x : BitVec 8) (h : x.toNat < 10) :
(x * 2).toNat < 20 := by
grind
```
値を2倍しても8 bitの範囲内へ収まるため、自然数側の上限を使えます。 同じ8 bitでも、型が持つbit operationと、自然数として得る値の範囲を、対応する変換規則へ分けて扱います。 solverへ手作業で`toNat`の補題を何段も追加する処理を、新しいhomomorphismの仕組みがまとめます。
### `[grind hom]`と`[grind hom_pred]`
`[grind hom]`を付けたtheoremは、source側の型のtermを、target側の型のtermへ変換する規則です。 `[grind hom_pred]`は、埋め込む値が持つ範囲の情報を供給します。 たとえば`Fin n`から自然数へ写す値には、`n`未満という上限も伴います。 演算の変換と、型が保証する値域を、異なる属性のtheoremから得ます。
変換は、E-graphへinternalizeする前にfixpointまで適用します。 途中の表現をすべて同じgraphへ加える代わりに、正規化後の形を使います。 以前の`Lean.Grind.ToInt`のtype classを中心とする構成では、同じ値の別の綴りとbridgeの等式が、ほかのsolverへ余計な情報として現れていました。 4.35では、当該type classの階層を削除し、変換規則と値域の情報へ整理しています。
当該変更に伴い、以前の変換と重複する`BitVec`や`UIntN`のE-matching lemmaも、既定の規則集合から外れています。 独自のmetaprogramが古いtype classや既定lemmaを前提にしている場合、単に同じtheorem名で探索する構成は移行対象です。 通常の`grind`ではhomomorphismの変換が既定で有効です。 `trace.grind.hom`と`trace.grind.hom.pred`は、適用した変換と値域情報を追うためのtrace classです。
## `Decidable`の判定値が、定義の簡約だけで`Bool`へつながる
`Decidable p`は、命題`p`が成立する側か、成立しない側の証拠を持ちます。 4.35より前は、`isTrue`と`isFalse`のconstructorを持つinductive typeでした。 4.35は`Bool`と、booleanの値に対応する`p`または`¬p`の証明を保持するstructureへ変えています。 真偽を計算する値と、命題が成立する根拠の対応を、構造の定義へ含めます([Decidableを変更するPR #8309](https://github.com/leanprover/lean4/pull/8309))。
変更は、同じ結論を異なるtheoremで証明できるという差だけではありません。 `decide`で命題をbooleanへ変換した式が、通常のboolean演算と定義上等しくなる範囲を増やします。 たとえば2個のbooleanが両方trueである命題は、booleanの`&&`へ簡約できます。 補助lemmaを適用する代わりに、`rfl`だけで等式を証明できます。
```lean
theorem decisionAnd (a b : Bool) :
decide (a = true ∧ b = true) = (a && b) := rfl
theorem decisionOr (a b : Bool) :
decide (a = true ∨ b = true) = (a || b) := rfl
```
左辺は`Prop`の連言や選言を判定する式で、右辺は`Bool`の演算です。 同じ真理値になると外から証明するだけなら、両辺の内部の定義が一致する必要はありません。 `rfl`が使える条件はより強く、定義を展開・簡約した値が一致します。 4.35の変更は、命題の判定とbooleanの計算を結ぶ際に、書き換えの証明を追加する必要を減らします。
`LawfulBEq`から提供する`DecidableEq`でも、`decide (a = b)`と`a == b`が定義上対応します。 booleanの等価比較と、命題としての等式を判定する実装を、同じ計算へ接続します。 ただし任意の独自`BEq`の返り値を、命題としての等式の証拠へ無条件に変える規則ではありません。 対応の根拠になる`LawfulBEq`の契約と、使用するinstanceが条件です。
独自の`Decidable` instanceでは、`decidable_of_bool`や`decidable_of_iff`が新しい表現へ対応する構築用の入口です。 `Decidable.isTrue`と`Decidable.isFalse`は`match_pattern`として残り、従来のpattern matchingを継続できる範囲を持ちます。 しかし実体がinductive typeであるという前提へ依存したmetaprogramまで、同じ内部構造を維持しません。 constructorを直接解析する実装と、公開するpatternの形を使うsourceでは、変更を受ける境界が異なります。
新しい`decide` tacticは、booleanを計算する間に証明まで運ぶ必要を減らします。 compiler側の`Decidable`専用の特別扱いも不要になりました。 一方、連言と選言の判定にはshort-circuitの評価順を保つ必要があります。 4.35は対応するinstanceへ`macro_inline`を付けています。 booleanへ寄せた表現でも、不要な側を評価しないcompile結果を保ちます([short-circuitを保つPR #15430](https://github.com/leanprover/lean4/pull/15430))。
## `rwa`は、書き換えた仮定でmain goalを閉じる
4.35の`rwa [rules]`は、最初のgoalを書き換えた後、`assumption`で同じgoalを閉じます。 `rwa [rules] at h`では、仮定`h`の型を書き換え、書き換え後の`h`でmain goalを閉じます。 以前のように、後段の`assumption`が関係のないgoalや別の仮定へ偶然一致する処理とは異なります。 書き換える場所と、結論を成立させる証拠の対応を限定しました([rwaの動作を変更するPR #14937](https://github.com/leanprover/lean4/pull/14937))。
```lean
theorem rewritten (n m : Nat) (h : n + 0 = m) : n = m := by
rwa [Nat.add_zero] at h
```
`h`を書き換えると`n = m`になり、main goalの型と一致します。 一方、書き換えた仮定が`n = n`にしかならなければ、`n = 0`の証明としては使えません。 すでに別の仮定`hp : n = 0`が存在していても、`at hq`を指定した`rwa`は`hp`へ切り替えません。 指定した仮定を使う経路が成立していない事実を、型の不一致として報告します。
```lean
example (n : Nat) (hp : n = 0) (hq : n + 0 = n) : n = 0 := by
rwa [Nat.add_zero] at hq
```
```text
Type mismatch: The rewritten hypothesis hq has type
n = n
but is expected to have type
n = 0
```
書き換えの途中でside goalを生成した場合は、side goalにも`assumption`を適用します。 閉じられない条件は残し、最初から存在した無関係な別のgoalには作用しません。 複数のgoalがある証明で、たまたま仮定と一致した別のgoalだけを消す動作を避けます。 移行後にerrorとなる証明では、main goalと`at`へ渡した仮定の型を対応付ける必要があります。
書き換えだけでreflexivityによってmain goalが閉じ、side goalもない場合は、`rwa`が不要なwarningを出します。 当該箇所は`rw`へ短くできます。 複数の場所を指定する`rwa [rules] at h₁ h₂`はdeprecatedになり、`rw [rules] at h₁ h₂ <;> assumption`へ分けます。 複数の仮定のうち1個を暗黙に採用する操作から、書き換えと終了処理を明示する操作への移行です。
## constructorの選択と、theoremの再記述を明示する
`constructor`は、適用可能なconstructorが複数あるとwarningを出すようになりました。 たとえば`2 ≤ 3 ∨ 3 ≤ 2`では、選言を作る入口の`Or.inl`と`Or.inr`の両方を、goalの外形へ適用できます。 左側の命題が成立するかは、constructorの適用後に残る証明です。 goalの外形へ合う入口が1個である条件とは異なります([constructorのwarningを追加するPR #14854](https://github.com/leanprover/lean4/pull/14854))。
```lean
theorem explicitConstructor : 2 ≤ 3 ∨ 3 ≤ 2 := by
left
decide
```
`left`なら`Or.inl`を選び、残る`2 ≤ 3`を`decide`で証明します。 以前の「最初に合ったconstructorを使う」動作を明示する入口は`constructor!`です。 証明するbranchが分かっている場合は、`left`や`right`でbranchを指定できます。 warningの条件はconstructorの候補の数であり、元の命題がfalseであるという診断ではありません。
`recall`も、追加のimportなしで使えます。 既存のtheoremを、指定した名前とstatementで再記述できるか検査するcommandです。 たとえば自然数の加算の可換性は、次の形で再記述できます([recallのPR #14899](https://github.com/leanprover/lean4/pull/14899))。
```lean
recall Nat.add_comm (n m : Nat) : n + m = m + n
```
右辺を誤って`n + m`へ変えた再記述は、簡単に証明できる反射的な等式でも拒否します。 `recall`が求めるのは、書いた命題が単独で真であることではなく、指定した既存theoremとの型の対応です。 命題の内容を省略した`recall?`には、検査に基づく再記述を提案する入口があります。 theoremの名前を覚えたままstatementだけを取り違える場合も、宣言の型で不一致を捉えます。
recursorの表示にも、定義上の計算規則が加わっています。 `#print Nat.rec`などで、zeroとsuccessorに対する簡約を対応付けられます。 recursorの引数の型だけでなく、constructorへ適用した際の計算も表示するため、生成された証明termを読む際の参照先が増えます([recursorの表示を変更するPR #14960](https://github.com/leanprover/lean4/pull/14960))。
## 大きいmodular powerを、結果の大きさへ抑えて計算する
`Nat.powMod b e m`は、`b`の`e`乗を`m`で割った余りを求める追加APIです。 先に巨大な自然数の累乗を作ってから余りを計算する代わりに、square-and-multiplyの各段階で余りへ戻します。 指数を2進の桁へ分け、二乗と必要な乗算を重ねるため、指数の値だけ乗算を繰り返す定義にもなりません。 compiled codeでは、GMPを使う構成へ対応する実装もあります([Nat.powModのPR #13490](https://github.com/leanprover/lean4/pull/13490))。
```lean
#eval Nat.powMod 2 1000000006 1000000007 -- => 1
theorem modularPower :
(2 : Fin 1000000007) ^ 1000000006 = 1 := by
decide
```
`Fin 1000000007`の値は、modulus未満の自然数と、上限の証拠を持ちます。 4.35では`Fin`の累乗も`Nat.powMod`を使うため、同じmodularな計算をkernelが簡約する経路へ接続します。 `#eval`の数値出力だけでなく、`decide`を含むtheoremも、kernelの型検査が受け付ける証明termになります。 大きい指数でも、余りを途中で取らずに累乗全体を構築する必要がありません。
`#eval`と`decide`は、同じ記法の計算を別の目的で使います。 `#eval`はcompileしたprogramを実行し、値を表示します。 `decide`は、命題の判定を使って証明termを作ります。 `modularPower`が依存するaxiomは、`#print axioms`で名前を取得できます。
```lean
#print axioms modularPower
-- => 'modularPower' depends on axioms: [propext, Quot.sound]
```
4.35は、旧式のin-kernel native reductionも削除しました。 対象は`Lean.reduceBool`、`Lean.reduceNat`、`Lean.ofReduceBool`、`Lean.ofReduceNat`、`Lean.trustCompiler`です。 kernelの内部からcompilerを動かして特定の式を簡約する旧経路は、2026年2月1日からdeprecatedでした。 `native_decide`と`bv_decide`は、native evaluationをaxiomとして明示する経路です。 内部では`Lean.Meta.nativeEqTrue`を使います([旧native reductionを削除するPR #14953](https://github.com/leanprover/lean4/pull/14953))。
当該削除は、`#print axioms`と`lake check`が扱う依存関係にも関わります。 旧定義はbodyから`Lean.trustCompiler`を常に参照し、単にimportした環境でも依存して見える問題がありました。 新しい検査では、実際の証明が許可されたaxiomだけへ依存するかを、宣言の依存関係へ沿って判定します。 native evaluationを許可する方針と、標準の3個だけを許可する方針は、同じaxiomの集合にはなりません。
## ArrayとVectorを、kernelでも計算できる範囲が広がる
4.35は`Array.ofFn`、`Array.map`、`Vector.map`などへ、kernelが使える計算の定義を加えています([Array.ofFnのPR #14989](https://github.com/leanprover/lean4/pull/14989))。 `Array.map`には、以前から`inline`と`expose`が付いていました。 しかし、内部から呼ぶ参照実装の`Array.mapM`は、通常の`def`のままでした。 `Array.mapM`のloopはlocalな再帰functionで、別moduleのkernelには展開できず、簡約が止まっていました。 4.35は参照実装と再帰helperの定義を公開し、import先でもloopを簡約できるようにします。 `Array.map`へ委譲する`Vector.map`も、同じ経路で計算できるようになります([mapのPR #14996](https://github.com/leanprover/lean4/pull/14996))。
`zipWith`と`modify`、`modifyM`も、対応する追加の対象です。 配列の要素ごとの結合や更新が証明中へ現れた場合に、compiled codeだけを持つ入口で計算が止まる経路を減らします。 ただしkernelの簡約は、実行時と同じallocationやpointer操作を再現する保証ではありません。 証明が参照する論理上の定義と、compile後に使う効率的な実装を対応付ける変更です([zipWithのPR #15078](https://github.com/leanprover/lean4/pull/15078)、[modifyのPR #15079](https://github.com/leanprover/lean4/pull/15079))。
`Vector`は、長さを型へ持つ配列です。 `Vector.map`は要素の型を変えても長さを保ち、計算する値と型に現れる長さの対応を維持します。 `Repr`の表示も`#v[...]`になり、通常の`Array`と区別できます。 長さ3の整数を10倍する表示例は、次の形です([Vectorの表示を変えるPR #14545](https://github.com/leanprover/lean4/pull/14545))。
```lean
#eval #v[1, 2, 3].map (· * 10) -- => #v[10, 20, 30]
```
`Vector`の`DecidableEq`も改善されています。 要素の等しさを計算して配列の等式を判定する処理と、長さの証拠を扱う処理が、証明を含むdataの計算へ重なります。 外形を通常のlistとして表示し直すだけの変更ではなく、固定長という型の契約を残した計算です([Vectorの等式判定のPR #14988](https://github.com/leanprover/lean4/pull/14988))。
## `bv_decide`の内部では、CNFと証明の保持量を減らす
`bv_decide`はbit vectorの命題をbooleanの問題へ変換し、SATによる結果を証明へ接続します。 4.35では、AIGに表れたif-then-else、XOR、XNORを識別する処理が加わりました。 同じ回路をすべてAND gateの組み合わせへ分解してから変換するより、少ないCNF clauseへ直接変換できます。 当該patternでは、12個のclauseが必要だった構成を4個で表せます([AIGのpatternを識別するPR #13968](https://github.com/leanprover/lean4/pull/13968))。
変換対象の回路へ該当するpatternが現れる場合に、solverへ渡す問題の表現が縮みます。 証明全体の時間には、回路を作る処理、SATの探索、得られた証明を検査する処理も含まれます。
LRATのcheckerも書き直されました。 clauseの表現でmemory使用量を減らし、entailment、unit propagation、RUP、RATなどの操作を公開するAPIへ整理しています。 探索するsolverと、返された証明を読むcheckerの役割を分け、検査側が扱うdataの構造を更新しています([LRAT checkerのPR #14842](https://github.com/leanprover/lean4/pull/14842))。
証明をtrimする処理には、元の証明と削減中の証明を同時に保持し、非linearな使い方になる問題の修正もあります。 不要な証明を捨てる処理でも、捨てる前の構造を別の参照が保持し続ければ、memoryを再利用できません。 4.35は、LRATを削減する経路の参照の持ち方も修正し、論理上のclause削除と実装上の保持量を接続しています([LRAT trimのPR #15124](https://github.com/leanprover/lean4/pull/15124))。
自然数をshift幅に使うbit vectorの式には、新しい符号化も加わりました。 `BitVec.ofNatClamped`を使って必要な幅へ制限し、SAT側では対応するbit vectorのatomとして扱います。 ただし、自然数のshift幅へ任意の算術条件を加えて解く機能ではありません。 bit vectorへの符号化で保持する情報と、自然数側で推論する情報の境界が残ります([symbolicなshift幅のPR #14928](https://github.com/leanprover/lean4/pull/14928))。
## bufferの変更を、uniqueな所有の条件へ対応付ける
Leanの`Array`や`ByteArray`は、値として扱いながら、uniqueに所有されている領域を更新へ再利用できます。 同じ領域を別の値が共有していれば、元の値を維持するためにcopyが必要です。 4.35の`markLinear`は、共有している値を必要に応じてcopyしてuniqueにし、以後の書き込みがlinearかを動的に追跡します。 対象には`String`、`ByteArray`、`FloatArray`、`Array`があります([markLinearのPR #15052](https://github.com/leanprover/lean4/pull/15052))。
`LEAN_ABORT_ON_NONLINEAR`を設定すると、mark後の書き込みがlinearでない場合にprogramをabortします。 `lean_internal_panic`へbreakpointを置けば、書き込みの経路をdebuggerで追えます。 `markLinear`は共有copyを無料にする最適化の指定ではありません。 最初に共有を解くcopyが必要な場合も含め、以後の更新がuniqueな所有を保つという実装上の前提を検出するための機能です。
compilerの`floatLetIn`にも、side effectやreferenceのlinearityへ関わる修正があります。 `EST.Out`や`ST.Out`のcaseを越えて`let`を移動すると、元の評価順序と参照の使い方を維持できない経路がありました。 4.35は移動してよい位置を制限し、値を早く計算する最適化がstateを扱う処理の意味を変える条件を除いています([letの移動を修正するPR #15089](https://github.com/leanprover/lean4/pull/15089))。
`ReduceArity`では、すべてのparameterを取り除く場合に、void parameterを残す修正があります。 functionとして呼ぶ値を、引数のない定数と同じ表現へ変えてしまう経路を避けます。 引数を削減できるという最適化上の性質と、呼び出し規約が保持するfunctionの区別を、変換後にも残す修正です([arityを修正するPR #15107](https://github.com/leanprover/lean4/pull/15107))。
closureの引数の個数にも、crashを修正する変更が含まれます。 arityが16以下のclosureへ16を超える引数を適用する経路で、argumentの処理が破綻する問題です。 sourceのfunctionを何段かへ分けて適用する構文と、runtimeが引数を運ぶ経路を、compile後にも一致させます([closureの適用を修正するPR #14969](https://github.com/leanprover/lean4/pull/14969))。
allocatorの設定では、mimallocの`MI_MAX_ALIGN_SIZE`を8へ設定しました。 `List.cons`のような24 byteのobjectを32 byteのallocation classへ丸めていた余分な領域を減らします。 listの要素やconstructorの論理的な型を変更せず、allocatorが予約する領域の区切りを調整する変更です([mimallocのalignmentを変更するPR #14948](https://github.com/leanprover/lean4/pull/14948))。
生成したC fileの書き込みも、temporary fileからrenameする構成になりました。 clangがC fileをmemory mappingして読む途中で同じfileを書き換えると、lexerが不整合な内容へ到達してcrashする場合があります。 4.35は完成した内容へatomicに置き換え、読む側へ書き込み途中のfileを渡す経路を減らします([C出力をatomicにするPR #14811](https://github.com/leanprover/lean4/pull/14811))。
`ST.Ref`では、値を取り出す`take`と、値を戻す`put`を使うcritical sectionへAPIを整理しています。 `put`を使う前提には、対応する値を先に`take`した状態が含まれます。 `take`と`put`の状態も、referenceを扱うAPIの条件になります([ST.RefのPR #14775](https://github.com/leanprover/lean4/pull/14775))。 別の参照操作では、`swap`と`get`のraceへcompare-and-swapを使う修正があります。 threadが一時的なnullの参照へ到達する経路を除きます([referenceのraceを修正するPR #14585](https://github.com/leanprover/lean4/pull/14585))。
## reference countのoverflowは、kernelのmemory safetyにも関わる
証明termの型検査も、objectの参照数や領域の寿命をruntimeで管理します。 4.35には、reference countがoverflowした場合の処理を修正する変更が含まれ、4.34.1にもbackportされています。 論理規則を変更する差分ではなく、規則を実行するkernelのdataが、解放後の領域を参照しないための修正です([4.35.0のruntime修正](https://lean-lang.org/doc/reference/latest/releases/v4.35.0/))。
1件目は、objectを連鎖的に解放するdeletion cascadeです。 overflowまたはunderflowでreference countを固定したobjectを、内部の解放経路が再び減算し、使用中に解放する可能性がありました。 18 GB以上の空きmemoryを持つmachineでは、official kernelのuse-after-freeへつながる可能性が修正対象です。 新しいguardは、persistentなobjectと、固定したsticky countの両方で解放を止めます([deletion cascadeを修正するPR #15241](https://github.com/leanprover/lean4/pull/15241))。
2件目は、同じsubtermを共有する`sharecommon_quick_fn`のincrementです。 `INT_MAX`を超える参照数でcountが巻き戻り、まだ参照しているsubtermを解放する可能性がありました。 100 GBを超える入力では、official kernelのundefined behaviorを排除できない経路です。 4.35はcountのoverflowを検出するとpanicし、使用中の領域を解放する処理へ進ませません([maximal sharingを修正するPR #15289](https://github.com/leanprover/lean4/pull/15289))。
3件目は、巨大な参照数を持つobjectをthread間で共有する処理です。 親のcountがoverflowしていると、子のobjectをthread-sharedへmarkしない経路がありました。 別threadが子のcountをnon-atomicに更新すれば、使用中に解放する可能性が生じます。 修正は、overflowした親からたどる子にも共有状態を伝え、参照数を更新する経路を合わせます([thread共有を修正するPR #15288](https://github.com/leanprover/lean4/pull/15288))。
既定のofficial kernelはsingle-threadで動くため、3件目の条件は、同じruntimeで並列に動くcheckerとは異なります。 `con-leche`のようにLean runtimeを使うcheckerは、同じruntimeの不具合を共有する範囲があります。 一方、`nanoda`や`con-ron`は、Lean runtimeに基づかないため、参照数の実装も同一ではありません。 `lake check --paranoid`が複数の実装を使う理由は、theoremのstatementを変えることではなく、同じ証明termへ異なる実装の型検査を適用できる点にあります。
## elaborationと構文の変更も、既存のmetaprogramへ影響する
instance searchは、要求された型との照合でmetavariableを割り当てられる範囲を修正しました。 instance transparencyで展開すれば一致する型を、表面の形の不一致だけで除外する経路を改善しています。 同時に、unificationでstuckした場合も、残るheuristicを試してから停止する変更があります。 instance searchでは、必要な展開を含む型の照合を改善します([instance searchのPR #14624](https://github.com/leanprover/lean4/pull/14624))。 unificationでは、計算のstuckと、残る手法を試しても解けない結果を分けます([unificationのPR #14583](https://github.com/leanprover/lean4/pull/14583))。
rangeの構文では、`1+2...3`を`(1+2)...3`として読むprecedenceへ変わりました。 `a...b|>.toList`も、range全体へpipelineを適用する読み方へ揃えています。 rangeを作る演算と、端の値を計算する演算の結び付きが変わるため、独自macroが受け取る構文木にも影響します([rangeのprecedenceを変更するPR #15054](https://github.com/leanprover/lean4/pull/15054))。
singletonの記法にも、継続行のindentationを要素の開始位置より深くする条件が加わりました。 行の境界が式の継続か、外側の構文へ戻る位置かを決める入力になります。 見た目だけの整形ではなく、複数行の式をparserがまとめる範囲へ関わる変更です([singleton記法のPR #14350](https://github.com/leanprover/lean4/pull/14350))。
syntaxの型では、`TSepArray`を`SepArray`へ統合し、separatorの`SourceInfo`を保持する構成になりました。 separatorを挟む要素の列へ、型情報のない`Syntax`の配列を暗黙に流すcoercionも整理されています。 macroが生成する構文の位置情報と、型を持つsyntaxの容器を、明示したAPIへ合わせる移行です。 separatorの位置情報と容器の型を、新しい表現へ合わせます([syntaxの配列を変更するPR #15020](https://github.com/leanprover/lean4/pull/15020))。 旧`withPosition`も削除されるため、macroで使う構築APIの名前も変更対象です([withPositionのPR #15019](https://github.com/leanprover/lean4/pull/15019))。
## Lakeは、失敗後のjobとdependencyの配置を選べる
`lake build --fail-fast`は、最初のrequired targetの失敗を受け、新しいbuild jobの開始を止めます。 すでに動いているjobはkillせず、完了するまで待ちます。 早い段階でerrorが出たworkspaceでも、未開始のdependency chainへ処理を広げ続ける必要を減らします([fail-fastのPR #14797](https://github.com/leanprover/lean4/pull/14797))。
```sh
lake build --fail-fast
```
停止判定は、`Job.mapM`と`Job.bindM`のcontinuationをdispatchする位置で行います。 失敗を共有するcancel tokenが設定されていれば、当該continuationへ進みません。 開始を見送ったjobと、実際に失敗したjobを分けるため、cancelしたmoduleへ架空の`bad import`を追加しません。 後続の表示改善では、開始しなかったtargetに`Canceled`の状態も表示します([cancelの表示を改善するPR #14835](https://github.com/leanprover/lean4/pull/14835))。
path dependencyには、`copy = true`の設定が加わりました。 通常のpath dependencyは、指定した相対pathのpackageを直接読みます。 copyを有効にすると、packageをworkspaceのpackages directoryへ複製し、複製先から読み込みます。 隣のcheckoutを直接buildする構成と、workspace内にdependencyの実体を置く構成を選べます([path dependencyのcopyを追加するPR #15142](https://github.com/leanprover/lean4/pull/15142))。
native codeの事前compileにも、粒度を分ける設定が加わっています。 `precompileImports`はmoduleがimportする側をcompileし、module自身を対象に含めません。 `precompileLibrary`は、library全体をimportする利用者向けにcompileします。 library内部の各moduleが、elaboration時に自分のimportをcompileする設定ではありません。 より広い`precompileModules`だけでは同時に選んでいた処理を、必要な境界へ限定できます([precompileの設定を分けるPR #15015](https://github.com/leanprover/lean4/pull/15015))。
dependency cacheでは、package単位のbuild記録を生成してuploadする入口が加わりました。 `lake build -o build.json --package name`で、対象packageの記録を作ります。 `lake cache put build.json --package=name`で、同じpackageを指定して記録を渡します。 projectの成果物を一括して扱う処理と、特定のdependency packageのcacheを供給する処理で、対象を指定できます。 cacheの記録とpackage名を合わせることで、別のpackageのbuild結果を同じupload対象へ混ぜる条件を減らします([package単位のcacheのPR #15141](https://github.com/leanprover/lean4/pull/15141))。
`lake lint`にはcode qualityを扱う経路も加わっています。 elaboration中のlinterが収集したentryを`.olean`へ保存し、build toolは再elaborationせずに各moduleのentryを取得できます。 通常のwarningとは別の情報として蓄え、CLIやeditorへ同じmessageを表示しません。 `warningAsError`の判定にも直接混ぜません([quality entryを保存するPR #14748](https://github.com/leanprover/lean4/pull/14748))。
package側の`package_code_quality_check`を登録する仕組みもあり、保存した指標へpackage固有の判定を加えられます。 moduleをもう一度compileして指標を取り直す処理と、既存のbuild結果から指標を集める処理を分けます。 build、証明のexportと再検査、保存したlinter情報の収集は、異なるcommandです。 同じworkspaceのartifactを、必要な検査や集計へ渡せます([Lakeのcode qualityを接続するPR #14933](https://github.com/leanprover/lean4/pull/14933))。
### Swift 6.4詳解——非同期のdefer、cancellation shield、noncopyableなcollection
URL: https://www.sakashita.page/swift-6-4-release-notes-and-analysis/
Last updated: 2026-10-25T13:00:00.000Z
Swift 6.4は2026年9月15日、6.4.1は10月13日に公開されました。 6.4では、`defer`内の非同期処理、cancellation shield、noncopyableな値を格納するcollectionが加わっています。 6.4.1は、optimizer、package解決、Foundation、testingの不具合を修正します([Swift 6.4](https://www.swift.org/blog/swift-6.4-released/)、[Swift 6.4.1の公開記録](https://forums.swift.org/t/announcing-swift-6-4-1/90029))。
コード例の対象は、macOS/arm64のApple Swift 6.4です。 compilerの識別文字列は`swiftlang-6.4.0.34.1`です。 言語modeは`-swift-version 6`です。 以下では、処理を終える順序、値の所有、参照のlifetime、生成物のdependencyへ分けて変更を説明します。
## `defer`から呼んだ非同期処理を、戻る前に完了させる
従来の`defer`は、scopeを出る際のcleanupを、準備した処理の近くへ書く構文でした。 複数の`defer`があれば、登録した逆順に実行します。 6.4では、周囲が`async`なcontextなら、body内で`await`できます。 呼び出し元へ戻る前に、非同期の`defer`も完了するまでawaitされます([SE-0493](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0493-defer-async.md))。
```swift
actor EventLog {
private var values: [String] = []
func add(_ value: String) { values.append(value) }
func snapshot() -> [String] { values }
}
func process(_ log: EventLog) async {
defer { await log.add("cleanup") }
await log.add("body")
}
```
`EventLog`は、eventを順に保存するactorです。 `process`の通常の処理で`"body"`を加え、終了時に`"cleanup"`を加えます。 呼び出し元が`"caller"`を加える時点では、`process`のcleanupも完了しています。
```swift
let log = EventLog()
await process(log)
await log.add("caller")
print(await log.snapshot()) // => ["body", "cleanup", "caller"]
```
以前は、非同期cleanupを`defer { Task { ... } }`へ包む方法が使われていました。 ただし独立したtaskを作るだけなら、元のfunctionが戻る際にtaskの完了を待つ関係を持ちません。 呼び出し元が次の処理へ進んだ後も、cleanupが実行中という順序になり得ます。 6.4の`await`を含む`defer`は、scopeを抜ける制御の経路へ非同期処理の完了を含めます。
同じcleanupを、通常return、早期return、errorを投げる経路へ個別に書く必要も減ります。 ただし`defer`のbodyからerrorを外へ投げる条件を、無制限に変えるproposalではありません。 非同期operationがthrowするなら、cleanup内部で失敗を扱う構成が必要です。 実行する時刻をscopeの終了へ結び付ける性質と、errorを処理する契約は、別の条件として残ります。
### suspensionは`defer`の実行時にも発生する
非同期の`defer`は、登録した位置でただちに実行する処理ではありません。 scopeを出る際に呼び、含まれるoperationがsuspendすれば、終了処理の途中でtaskがsuspendします。 元のfunctionのbodyに見える最後の`await`より後でも、cleanupの非同期呼び出しによってsuspensionが生じます。 呼び出し元へ戻るまでが、最後の通常statementだけで決まる構成ではなくなります。
新しい`defer async`というkeywordは導入していません。 実際の非同期呼び出しへ付ける`await`と、周囲の`async`なcontextで条件を表します。 closureなら、`defer`内の`await`がclosureの`async`を推論する根拠にもなります。 宣言したfunctionが同期のままなら、body内の`defer`だけを非同期へ変えることはできません。
同期functionにasyncな`defer`を置くと、6.4でもerrorになります。
```swift
func cleanup() async {}
func process() {
defer { await cleanup() }
print("body")
}
// error: 'async' defer must appear within an async context
```
scope終了時の暗黙のawaitも、同期functionの公開型からは実行できないためです。 新しい構文が許すのは、すでに非同期処理を表せるcontextでの終了処理です。
`defer`のbodyは、周囲のscopeのisolationを引き継ぎます。 非同期cleanupのためだけに、無条件で別actorやexecutorへ移る規則ではありません。 別actorのmethodを呼ぶなら呼び出し先の条件でsuspendし、同じisolation内の処理なら必要なaccessを維持します。 cleanupを独立taskへ移す従来の回避策と、元のscopeへ留める構文では、実行する文脈も異なります。
## cancellation shieldの内部では`Task.isCancelled`が`false`を返す
非同期の`defer`を追加しただけでは、cancelされたtaskの状態は変わりません。 cleanupが呼ぶfunctionで`Task.checkCancellation()`を使うと、`CancellationError`をthrowし、必要な処理より先に終了する場合があります。 6.4の`withTaskCancellationShield`は、scope内で`Task.isCancelled`に`false`を返させます。 `Task.checkCancellation()`によるthrowも抑止します。 task自体のcancellationを取り消す操作ではありません([SE-0504](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0504-task-cancellation-shields.md))。
```swift
withUnsafeCurrentTask { $0?.cancel() }
let before = Task.isCancelled
let inside = withTaskCancellationShield { Task.isCancelled }
let after = Task.isCancelled
print(before, inside, after) // => true false true
```
shieldへ入る前はcancel済みで、shield内のstaticな`Task.isCancelled`は`false`を返します。 shieldを出た後の`Task.isCancelled`は、元のcancel済み状態に対応する`true`を返します。 途中でcancel要求を破棄してtaskを未cancelへ戻す処理なら、最後の値を説明できません。
shieldには、同期のclosureを受け取る版と、非同期のclosureを受け取る版があります。 同期のcleanupのためだけに、新しい非同期taskを作る必要はありません。 非同期版でも、元のtaskから独立したunstructured taskへ処理を移すことなく、shieldのscopeを実行します。 task-localな文脈とcleanupの完了を、元の実行経路へ保持できる構成です。
### task handleから読む状態とは異なる
`Task.isCancelled`は、現在の実行scopeの状態を読むstaticな入口です。 一方、外部から取得した`task.isCancelled`は、特定のtaskの実際のcancel状態を返します。 shield内で実行している途中でも、外部のhandleから読む値を未cancelへ反転しません。 taskの実行がshieldへ入ったり出たりするたびに、外部から状態が変わって見える動作を避けています。
`withUnsafeCurrentTask`から得るhandleの`isCancelled`も、taskの実際のcancel状態を返します。 現在のtaskがshield内かを調べる`hasActiveCancellationShield`も追加されました。 staticな`Task`と`UnsafeCurrentTask`の両方に、`hasActiveCancellationShield`があります。 `isCancelled`と組み合わせれば、taskへ届いたcancel要求と、shieldの有効期間を区別できます。
shield内から自分自身をcancelしても、同じscopeの`Task.isCancelled`は`false`を返せます。 要求を送るoperationと、要求を読むoperationが、同じ条件を使うわけではないためです。 shieldが終了すると、要求されたcancellationが残ります。 cancelの恒久的な取り消しを公開するAPIを追加したものではありません。
### child taskとcancellation handlerの境界
shield内で作り、shield内でlifetimeを扱うstructured child taskには、外側からのcancelの自動伝播を遮ります。 `async let`やtask groupを使うcleanupでも、親がcancel済みという理由だけで、開始直後からcancel済みになる経路を避けられます。 ただしtask groupの`cancelAll()`など、child側を明示的にcancelする操作は有効です。 外側の要求から守るscopeと、child自身へ送る要求は、同じものとして消去しません。
task groupの`addTask`を同期のshieldで包むだけでは、childの実行scopeを覆いません。 追加したchildは、closureの呼び出しが終わった後も動くためです。 特定のchildで`Task.checkCancellation()`のthrowを抑止するなら、childのbody内へshieldを置きます。 scopeの字面がtaskを作る式へ接していることより、shieldが実際の計算を囲むlifetimeが意味を持ちます。
shieldが有効な間に登録したcancellation handlerも、shieldによる制御を受けます。 cleanupが呼ぶ下位のAPIで登録したhandlerも、shield内では外側のcancelを受けて即座に実行されません。 既存のすべてのtaskへhandlerの抑止を広げる操作ではなく、shieldで囲んだ現在のtaskのscopeが対象です。 下位の処理が自分で持つtimeoutや別のerrorまで、同じ成功条件へ変えるわけではありません。
`defer`とshieldを組み合わせると、cleanupの完了をawaitしながら、内部でcancelを理由にthrowする経路を抑止できます。 `defer`はcleanupを開始して完了を待つ順序を定め、shieldはcleanup内部でcancelを理由に短絡する条件へ作用します。 shieldはruntimeの変更も使うため、proposalは古いruntimeへのback deploymentを対象にしていません。 compilerだけを更新して古いOS上のruntimeまで同じAPIを使える条件にはなりません。
## optionalな`some`と`any`の括弧を減らす
6.4では、`some P?`を`(some P)?`、`any P?`を`(any P)?`と同じ意味で書けます。 以前は`P?`をconstraint側へ結び付け、protocolではない`Optional
`を指定するerrorになりました。 末尾の`?`が、opaque typeやexistential typeの全体へ適用される構文です。 protocol自体をoptionalにするという新しい型constructorを導入するわけではありません([SE-0521](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0521-improved-optional-opaque-and-any.md))。
```swift
protocol Label {
var text: String { get }
}
struct Item: Label {
let text: String
}
func make(_ present: Bool) -> some Label? {
present ? Item(text: "ready") : nil
}
let first: any Label? = Item(text: "ready")
let second: any Label? = nil
print(first?.text ?? "absent", second?.text ?? "absent") // => ready absent
print(make(true)?.text ?? "absent", make(false)?.text ?? "absent") // => ready absent
```
`some`の返り値では、隠れた具体型は`Item`であり、callごとに任意の別実装へ切り替える契約ではありません。 `any`はprotocolに合う値を格納するexistentialで、optionalという外側の容器を簡潔に書けるようになっています。
複数protocolを合成する場合には、括弧が残ります。 `any (P & Q)?`は書けますが、`any P & Q?`を同じ意味として無条件には受け付けません。 postfixの`?`が長い合成全体へ作用する範囲を、構文へ明示するためです。 括弧の削減は、任意の型式から括弧を外せる一般的な優先順位の変更ではありません。
既存の`(any P)?`や`(some P)?`は同じ型を表すため、ABI上の別の型へ変わりません。 新しいcompilerが出す`.swiftinterface`では、古いcompilerが読めるよう、括弧付きの綴りを維持します。 開発者がsourceで選ぶ表記と、module interfaceを別versionのcompilerへ渡す表記を分けています。 単に新構文で書いた全公開型を、新構文の綴りで配布する構成にはなりません。
## `@diagnose`でwarningの適用範囲を宣言へ限定する
`@diagnose`は、指定したdiagnostic groupを、宣言のscopeでerror、warning、ignoredへ設定するattributeです。 command lineのmodule全体の設定に対し、sourceの宣言単位でseverityを指定できます。 functionならbodyだけでなく、signatureも対象になります。 元からerrorとして出る診断を、warningの設定で無効化する機能ではありません([SE-0522](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0522-source-warning-control.md))。
```swift
@available(*, deprecated, message: "Use modern()")
func legacy() {}
@diagnose(DeprecatedDeclaration, as: error)
func adapter() {
legacy() // error: 'legacy()' is deprecated: Use modern()
}
```
`adapter`内のdeprecatedなfunction呼び出しは、errorになります。 診断groupは`DeprecatedDeclaration`として表示され、attributeで指定した対象へ一致しています。 project全体をwarnings-as-errorsにしなくても、特定の宣言で同じgroupを厳しく扱えます。
内側の宣言へ同じgroupの設定を付けた場合は、外側のscopeから受けた設定を精密化できます。 groupにsubgroupがある場合は、subgroupだけを対象にする設定も可能です。 別のgroupに属するwarningまで、同じattributeへ巻き込まれるわけではありません。 `reason:`には文字列literalを付けられますが、string interpolationで動的な理由を生成する構文ではありません。
importへ付けたattributeは、import statement自身が出すwarningへ作用します。 import先のlibraryのすべての宣言へ、利用者のseverity設定を遡って適用する意味ではありません。 macroが生成する宣言に対する設定なども含み、attributeを付ける構文ごとに対象のscopeが定まっています。 診断を受けるsourceの位置と、既定のmodule設定との優先関係を、公開された規則で限定しています。
## `UniqueArray`は、配列と要素の所有を1個へ保つ
通常の`Array`はcopyableで、複数の値がstorageを共有した後、変更時にcopy-on-writeで独立させます。 しかし要素がnoncopyableなら、共有したstorageから要素を複製して分離する経路を作れません。 6.4の`UniqueArray`は、配列自体をnoncopyableにし、storageの所有者を1個へ保ちます。 要素にも`Copyable`を要求せず、所有を移すoperationで格納できます([SE-0527](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0527-rigidarray-uniquearray.md))。
```swift
struct Ticket: ~Copyable {
var number: Int
}
func demo() {
var tickets = UniqueArray()
tickets.append(Ticket(number: 7))
tickets.append(Ticket(number: 11))
print(tickets.count)
for ticket in tickets {
print(ticket.number)
}
}
demo()
```
```text
2
7
11
```
`UniqueArray`は追加のmoduleをimportせずに使えます。 `append`は、新しい`Ticket`の所有を配列へ移します。 loopは要素をcopyする代わりに借りて読むため、`Ticket`に`Copyable`を要求しません。 通常の`Array`は要素に`Copyable`を要求するため、同じ`Ticket`を格納できません。
```swift
let tickets = [Ticket(number: 7)]
// error: generic struct 'Array' requires that 'Ticket' conform to 'Copyable'
```
配列の代入もcopyとは異なります。 `let second = first`でnoncopyableな配列を移すと、`first`は消費されます。 移動後の`first.count`は、consume後の再利用として拒否します。
```swift
func invalid() {
var first = UniqueArray()
first.append(Ticket(number: 7))
let second = first
print(second.count)
print(first.count)
}
// error: 'first' used after consume
```
所有権の診断を含むcompileには、`swiftc -swift-version 6 -c UniqueFailure.swift`を使います。 単にruntimeで参照数がたまたま1になっている配列ではなく、sourceの所有関係をcompilerが制約します。

UniqueArrayをfirstからsecondへ移すと所有者が変わり、firstの再利用は拒否され、要素の走査はborrowで行う図
copy-on-writeの配列では、他の値がstorageを共有するかが、変更時にcopyする条件へ関わります。 `UniqueArray`の代入は所有を移すため、代入後の共有storageを変更時にcopyして分離する経路を作りません。 一方、capacityを超える追加では、新しいbufferを確保して要素を移す処理が必要です。 所有がuniqueである条件と、追加時に常にallocationがない条件は独立しています。
### capacityの成長と、実際のoperationの計算量
`UniqueArray`は動的にcapacityを増やす配列です。 追加が必要なcapacityを超えた場合、既存の要素を含む新しいcontiguousなstorageへ移ります。 capacityを幾何的に成長させることで、連続する追加のamortizedな計算量を保ちます。 成長率は実装の詳細で、特定の倍率をsourceの契約として固定するAPIではありません。
したがって、copy-on-writeを外した配列でも、単発の`append`が要素数に比例する場合は残ります。 sharedなcopyを分離するための移動と、capacityを増やすための移動は、発生する条件が異なります。 noncopyableな要素でも所有を新しい領域へ移せるため、動的な成長を実装できます。 各要素の独立したcopyを作れない性質は、所有を保った移動を禁止する性質ではありません。
従来の`Array`を、要素がcopyableかによって条件付きでcopyableにする案もproposalで検討されています。 genericなcontextでは、実際の要素がcopyableかをruntimeで判定し、copy-on-writeの経路を選ぶ必要が生じます。 noncopyableを許す宣言だけでは、実際の型argumentが必ずnoncopyableとは決まらないためです。 `UniqueArray`は別の型として所有の条件を固定し、既存の`Array`の変更operationへ新しい判定を一括で加える構成を避けています。
配列を受け取るAPIも、所有の使い方を型へ反映します。 読むだけならborrowし、以後使わない所有を渡すならconsumeし、内容を更新するならmutatingなaccessを要求します。 noncopyableな要素を通常のcopyableな値として返すAPIへ、無条件には適合しません。 collectionの選択は、格納できる要素だけでなく、受け取った値を呼び出し側が使い続けられる条件も変えます。
## `UniqueBox`は、heap上の1値をuniqueに所有する
`UniqueBox`は、heapへ置いた1個の値をuniqueに所有するsmart pointerです。 中身はnoncopyableでも格納できます。 box自体もnoncopyableで、使用が終わると中身の破棄と領域の解放を担当します。 手動のpointerで必要だったinitialize、deinitialize、deallocateの組を、所有する値のlifetimeへまとめます([SE-0517](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0517-uniquebox.md))。
```swift
var box = UniqueBox(Ticket(number: 42))
print(box.value.number) // => 42
box.value = Ticket(number: 43)
print(box.value.number) // => 43
```
`value`への読み取りは、中身を独立した`Ticket`へcopyしません。 更新時は、元のpayloadを新しい値へ置き換えます。 classでboxを作る方法と異なり、複数のownerが同じboxを参照する契約を、既定の容器へ含めていません。
boxの値を別の変数へ移しても、heap上のpayloadのaddressは移動しません。 boxの所有を保持する側が変わることと、payloadの格納場所を変更することは独立しています。 ただしproposalは、raw pointerを取得する公開APIを同時には追加していません。 安定したaddressを内部で持つ性質から、任意のunsafe pointerを取り出して外へ保存する操作まで許可されたとは扱いません。
payloadの所有を取り出す`consume()`は、boxを消費します。 copyableなpayloadには、明示的に別のboxを作る`clone()`もあります。 普通の代入が暗黙の共有copyになる構成と、明示的なcloneで新しい所有を作る構成を分けています。 大きい値をheapへ間接化する目的と、値を複数箇所で共有する目的は、別の容器で表せます。
## `Iterable`は、要素を返す代わりに`Span`を渡す
従来の`Sequence`は、iteratorの`next()`が`Element?`を返します。 返り値として要素を所有する形では、noncopyableな要素を残したまま、独立した次の値を渡すことができません。 6.4の`Iterable`は、連続した要素を参照する`Span`をiteratorから受け取り、要素をborrowして走査します。 前述の`UniqueArray`のloopは、要素のcopyを要求しない走査の例です([SE-0516](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0516-borrowing-sequence.md))。
`Iterable`自身も、noncopyableまたはnonescapableな型へ適合できます。 elementにも`Copyable`を要求しません。 `makeBorrowingIterator()`が返すiteratorは、走査対象のlifetimeへ依存します。 配列などのownerが使えなくなった後まで、iteratorだけを保存して読み続ける契約ではありません。
iteratorの中心になるoperationは、`nextSpan(maxCount:)`です。 最大の要素数を受け取り、次の連続領域を表す`Span`を返します。 空の`Span`は走査の終了で、終了後の呼び出しも空を返す必要があります。 1要素ずつ返すAPIと異なり、連続領域を持つcollectionでは、複数要素のaccessを1回の取得へまとめられます。
ring bufferのように物理的な領域が途中で折り返す場合は、複数の`Span`を返せます。 最初は開始位置からbufferの末尾、次はbufferの先頭から最後の要素までです。 storage全体を常に1個の連続領域として公開する必要はありません。 rangeのように要素を都度生成する型なら、1要素の`Span`を繰り返し返す構成も可能です。
### 次の`Span`を取得すると、以前のaccessは終了する
返した`Span`は、iteratorのexclusive accessへlifetimeが依存します。 `nextSpan`や別のmutating operationを再び呼ぶ前に、以前の`Span`を使うaccessを終える必要があります。 iteratorが次の要素を生成する際、同じ一時storageを再利用する実装も表せるためです。 前のelementへの参照を保存したまま次へ進める構成を、一般のiteratorへ要求しません。
noncopyableな要素をloopの外へ保存する処理は、借りた値のlifetimeを越える場合があります。 一般的な`first(where:)`が単に要素を返せない理由も、内部で作ったiteratorのlifetimeへ結果が依存するためです。 copyableな要素ならcopyを作って返せますが、noncopyableまで許すgenericな実装へ同じ処理を拡張できません。 走査しながら読む契約と、選んだ値の所有を外へ渡す契約を分ける必要があります。
`maxCount`は、単なる性能上のhintではありません。 iteratorを別のfunctionへ`inout`で渡し、必要な個数だけ処理して返す用途で、取り出す上限を指定できます。 大きい`Span`を無条件に返すAPIでは、呼び出し側に、使わなかった残りの位置を保持する処理が必要です。 bulkな走査と、途中で処理を区切るoperationを、同じiteratorのstateへ合わせるためのparameterです。
### throwing iterationと`Failure`
`Iterable`と`BorrowingIteratorProtocol`には、`Failure: Error`というassociated typeがあります。 既定は`Never`なので、単に格納済みの要素を読むcollectionなら、loopへ`try`を加えずに使えます。 生成やaccessで失敗するiteratorなら、`for try element in iterable`と書きます。 genericなalgorithmも、`throws(Failure)`で失敗する型を保持できます。
iteratorがerrorを投げた後の継続動作は、一般のprotocolが一律には保証しません。 呼び出し元が同じiteratorへ再び`nextSpan`を呼び、必ず次の要素から回復できる契約ではありません。 errorより前の要素を失わないよう、先行するelementの`Span`はerrorを投げる前に返す規則があります。 走査の終了を示す空の`Span`と、失敗を投げる経路の後状態は、別の契約として定義されています。
## `borrow`と`mutate` accessorで、stored valueへのaccessを公開する
6.4は、computed propertyとsubscriptへ`borrow`と`mutate` accessorを追加します。 `get`のように独立した値を返す代わりに、ownerが持つstored valueを借りて公開します。 `mutate`では、更新可能なexclusive accessとして`&`を付けた値を返します。 payloadがnoncopyableでも、中身をcopyせずにproperty経由で読み書きできます([SE-0507](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0507-borrow-accessors.md))。
```swift
struct Wrapper: ~Copyable {
var storage: Element
var value: Element {
borrow { storage }
mutate { &storage }
}
}
```
`Wrapper`のpropertyを通じた更新は、保持する`Ticket`の同じfieldを書き換えます。
```swift
var value = Wrapper(storage: Ticket(number: 21))
print(value.value.number) // => 21
value.value.number += 1
print(value.value.number) // => 22
```
利用側のproperty accessは通常の記法でも、accessorは独立した`Ticket`をcopyして返していません。 値を読む式と、値の所有を別の変数へ移す式の差が、propertyの実装にも現れます。
`borrow`から返すのは、accessorが戻った後もstorageが有効な値です。 accessor内で作ったlocalな配列を返したり、temporaryなoptionalを構築して返したりする処理は許しません。 ownerの次のmutationまで有効なstorageを公開するという、lifetimeの条件があるためです。 任意の新しい値を計算して返せる`get`のbodyを、keywordだけ変えて同じ契約にできるわけではありません。
`yielding borrow`や`yielding mutate`は、accessの前後へ処理を置けるcoroutineです。 temporaryな値を構成し、accessの完了後に片付ける用途も表せます。 新しいborrow accessorはcoroutineを使う必要を減らせますが、公開できるstorageの条件が強くなります。 temporaryを生成する柔軟さと、保存済みの値へ直接accessする仕組みで、使用できる実装が異なります。
borrowが有効な間は、ownerを矛盾する形で変更できません。 mutableなaccessが有効なら、同じownerへ別のaccessを重ねることも制限されます。 copyを避けるために内部の値を公開しながら、元のstorageを変更して参照を無効にする経路を防ぎます。 propertyへ`borrow`を付ける意味は、単なるinline最適化の希望ではなく、accessとlifetimeの契約です。
## `Ref`と`MutableRef`は、1値への参照を値として扱う
`Ref`はsharedな読み取り、`MutableRef`はexclusiveな読み書きを表します。 両方ともnonescapableなので、参照先のlifetimeを越えて自由に保存する型ではありません。 `Ref`はcopyableですが、copyしても参照先の所有を新しく作りません。 `MutableRef`自体はnoncopyableで、同じexclusive accessを無条件に複製しない構成です([SE-0519](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0519-ref-mutableref-types.md))。
```swift
var number = 10
do {
var reference = MutableRef(&number)
reference.value += 5
print(reference.value) // => 15
}
print(number) // => 15
```
scope内では`reference.value`を通じて同じ整数を更新し、scopeを出ると元の`number`へaccessが戻ります。 更新した値を別のboxからコピーし直す処理ではありません。 `inout`のfunction呼び出しの中だけに限定されていたexclusive accessを、lifetime付きの値として持てます。
dictionaryのentryへ何回も加算する用途なら、`MutableRef(&dictionary[key, default: 0])`でaccessを一度形成できます。 以降は`reference.value`へ加算し、毎回同じkeyでlookupする式を繰り返さずに済みます。 ただし参照の有効期間中は、dictionary本体へ矛盾するaccessを重ねません。 hash tableを変更してentryのstorageを移す処理と、取得済みのentryを更新する処理を、同時に許す契約にはなりません。
### noncopyableな`Optional`の追加APIの公開範囲
`Optional`へ`ref`、`mutableRef`、`put()`を加える機能は、SE-0532で提案されています。 2026年10月24日時点のstatusは`Accepted`で、対応する実装PR #88505は未mergeでした。 Apple Swift 6.4の`Ticket?`には、`ref`が存在しません([SE-0532](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0532-optional-noncopyable-improvements.md)、[実装PR #88505](https://github.com/swiftlang/swift/pull/88505))。
```swift
func inspect(_ ticket: borrowing Ticket?) {
if let reference = ticket.ref {
print(reference.value.number)
}
}
// error: value of type 'Ticket?' has no member 'ref'
```
提案する`ref`は、optionalが値を持つ場合だけ、payloadへの読み取り参照を返します。 `mutableRef`なら更新可能な参照で、optional自体を消費せずに中身を扱う目的です。 従来の`if let`でnoncopyableなpayloadを取り出すと、optionalを消費する経路になり得ます。 新しい参照の形は、値が存在する条件と、payloadを借りるaccessを同じ式で表すための追加です。
提案前の書き方では、noncopyableなoptionalの`switch`でpayloadをborrowする方法があります。 読み取りでpayloadを消費せず、optionalを引き続き使用できます。 `UniqueArray`の走査、propertyのborrow accessor、optionalのpayloadのaccessは、いずれもcopyを要求しない方向の機能です。 ただし`UniqueArray`やborrow accessorと異なり、SE-0532の追加memberは6.4のApple toolchainには存在しません。
## 一時bufferの初期化と破棄を`OutputSpan`へまとめる
`withTemporaryAllocation`は、一時的な領域を`OutputSpan`または`OutputRawSpan`として渡します。 未初期化の領域を持つraw pointerを直接渡す代わりに、初期化した要素数をspan側で追います。 closureが戻ると、初期化済みの要素を破棄し、領域のlifetimeを終えます。 throwする経路でも、初期化した部分だけを適切に片付ける構成です([SE-0524](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0524-span-temporary-allocation.md))。
```swift
let total = withTemporaryAllocation(of: Int.self, capacity: 3) { output in
output.append(1)
output.append(2)
output.append(3)
return output.span[0] + output.span[1] + output.span[2]
}
print(total) // => 6
```
`capacity: 3`は、最初から3個の`Int`を初期化する指定ではありません。 `append`で初期化した部分が増え、`output.span`は初期化済みの値を読む入口になります。 確保した領域の広さと、実際に有効な値を置いた範囲を、同じ整数として扱いません。
以前の`withUnsafeTemporaryAllocation`では、初期化したcountを利用者が追い、終了時のdeinitializeを合わせる必要がありました。 途中でthrowした場合、初期化済みの範囲を間違えると、破棄漏れや未初期化の値の破棄へつながります。 新APIは、追跡する初期化状態とcleanupを、容器のoperationへ含めます。 temporaryなallocationをstackへ置く最適化を使えることと、すべての入力でstack allocationを保証することは別の条件です。
raw byte用の版は、byte数とalignmentを受け取り、`OutputRawSpan`を渡します。 typedな版は要素型とcapacityを受け取り、要素数を単位にします。 同じ整数を引数へ渡しても、byteの個数と要素の個数では確保する範囲が異なります。 bufferへ有効な値を書き込むoperationと、初期化済みの領域を読み取るoperationを、型に沿って選びます。
## `RawSpan`のsafeなloadは、任意の型の復元ではない
raw byteから値を読むAPIには、`ConvertibleFromBytes`という制約を使います。 任意のbit patternが有効な値になる型が対象です。 書き出す側の`ConvertibleToBytes`は、strideに含まれるbyteがすべて初期化される条件を持ちます。 読める型と、全byteを安全に公開できる型を分けています([SE-0525](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0525-rawspan-safe-loading-api.md))。
整数は、bit patternを有限幅の値として解釈できます。 一方、`Range`は2個の整数を持っていても、下限が上限以下という意味上の条件があります。 任意の2整数のbit列を読むだけで、有効な`Range`になるとは決まりません。 fieldが整数だけという構造上の一致は、任意のbyte列から復元してよい条件と同じではありません。
書き出し側では、paddingを持つ型も問題になります。 stored propertyへ値が入っていても、stride全体のpaddingまで初期化済みとは限りません。 `ConvertibleToBytes`は、未初期化のbyteを公開する経路を排除するための条件です。 `BitwiseCopyable`だけで、任意の外部byte列の読み込みと、stride全体の書き出しを両方許可しません。
新しい`load(fromByteOffset:as:)`は、boundsを検査し、pointer alignmentの制約を要求しない読み込みです。 fixed-width integerには、`ByteOrder.bigEndian`や`.littleEndian`を指定する版もあります。 network formatやfile formatが定めるbyte順と、hostのnativeなbyte順を、APIへ渡せます。 safeなloadはatomic operationではないため、複数threadの同期まで同じ呼び出しへ含めるものではありません。
## Subprocess 1.0は、streamと終了結果のAPIを確定する
Subprocess 1.0.0は2026年8月4日に公開された、processを起動するSwift packageです。 Swift 6.4の公開紹介でも、関連するlibraryの更新として取り上げられています。 compilerに組み込まれたstatementではなく、`Package.swift`のdependencyとして追加して使います。 以下のコード例のdependencyは`swift-subprocess` 1.0.0です([Subprocess 1.0.0の公開記録](https://github.com/swiftlang/swift-subprocess/releases/tag/1.0.0))。
```swift
import Subprocess
let result = try await Subprocess.run(
.path("/bin/echo"),
arguments: ["release-demo"],
output: .string(limit: 1024),
error: .string(limit: 1024)
)
print(result.standardOutput, terminator: "") // => release-demo
print(result.terminationStatus.isSuccess) // => true
```
1.0.0の`standardOutput`は、optionalを外した`String`です。 以前のAPIが`String?`を返していたため、古い例ではunwrapが必要でしたが、1.0.0では不要です。 decode時は不正なbyte列をUnicode replacement characterへ置き換えるため、不正なencodingを`nil`として返す契約ではありません。
`limit: 1024`は、収集する出力のbyte数の上限です。 出力をmemoryへすべて貯める場合も、無制限のcollectionを既定の前提にせず、上限を指定します。 長い出力を順に処理する用途には、`.sequence`でstreamとして受け取る入口があります。 全体を収集した文字列と、順に消費するstreamは、返す値の型とmemoryの使用条件が異なります。
processの起動・I/Oに関するerrorと、起動したprogramが非0のcodeで終了する結果も異なります。 `/bin/sh`へ`exit 7`を渡しても、processの終了結果を返り値として取得できます。
```swift
let failed = try await Subprocess.run(
.path("/bin/sh"),
arguments: ["-c", "exit 7"],
output: .discarded,
error: .discarded
)
print(failed.terminationStatus.isSuccess) // => false
```
`try`が付いているという理由だけで、非0の終了codeがすべてSwiftのthrowへ変わるわけではありません。 終了codeを判定する処理には、返り値の`terminationStatus`を使います。
Unix系では、通常の終了とsignalによる終了を、異なるcaseで表します。 `isSuccess`は、成功した通常終了を簡潔に検査する入口です。 stdoutへ期待した文字列が出ていても、後からerrorで終了するprogramはあり得るため、出力の有無を終了結果の代わりにはしません。 `standardOutput`と`terminationStatus`は、出力された文字列とprocessの終了状態を別のpropertyで返します。
### 1個の`Execution`から、必要なstreamへaccessする
1.0では、`Execution`がinput、output、errorの型をparameterに持ちます。 `input: .inputWriter`なら`standardInputWriter`、`output: .sequence`なら`standardOutput`へaccessできます。 stdinだけ専用の別closure引数を使っていた以前の構成から、3個のstreamを同じ`Execution`へまとめています。 すべてのstreamを必ずpipeへするのではなく、設定に対応するpropertyを型の条件で公開します。
返り値も、`ExecutionResult`へ統合されています。 closureでstreamを読みながら、stderrだけは文字列へ収集する組み合わせを表せます。 closureの計算結果は`closureResult`へ保持し、processの終了結果や収集した出力とは別のfieldになります。 streamを選んだために収集結果を返せないという、以前の返り値の分離を減らしています。
streamの`SubprocessOutputSequence`は、OSのpipeを所有するsingle-passな値です。 同じstreamからiteratorを2回作る操作は許さず、複数のconsumerへ暗黙に同じbyte列を複製しません。 textへ変換する`.strings()`は、複数byteの文字がread bufferの境界で切れた場合も、つなぎ直してdecodeします。 個別のread結果を毎回独立した文字列へ変換する処理では、UTF-8の文字の途中を不正な入力として扱う経路が生じます([1.0のstreamとtext decodeの変更](https://github.com/swiftlang/swift-subprocess/releases/tag/1.0.0))。
### executableの名前とpathは、異なる解決方法
`.path(...)`は、使用する実行fileの位置を渡します。 `.name(...)`は、`PATH`にあるdirectoryを順に探索して実行fileを選びます。 1.0では、subprocessへ渡す環境の`PATH`を優先し、current directoryや相対的な`PATH`のentryを暗黙の探索先へ含めません。 名前にpath separatorがある場合も、current directoryからの相対pathとして処理しない規則です。
Windowsでも、`PATH`が存在する場合は、applicationのdirectoryやsystem directoryへ無条件に探索を広げません。 applicationと一緒に配布したhelperを使うなら、場所を`path`で指定する構成になります。 `PATHEXT`の扱いも揃え、拡張子を置換する代わりに付加する探索を使います。 同じprogram名でも、processへ渡す環境と探索規則が、起動するbinaryを決める条件になります。
## SwiftPMはSwift Buildを既定にする
6.4のSwift Package Managerは、Swift Buildを既定のbuild engineとして使います。 `--build-system`の既定は`swiftbuild`で、従来の`native`と`xcode`はdeprecatedです。 Swift Buildは、targetのdependencyや実行するbuild operationを扱うengineです。 package manifestが表すdependencyを、platformに応じたcompileとlinkのjobへ移す層に関する変更です([SwiftPM 6.4の紹介](https://www.swift.org/blog/swift-6.4-released/))。
manifestを同じSwiftで書けることと、各OSで必要なtoolchainやSDKが同じであることは異なります。 Swift Buildを共通のengineへしても、C/C++のcompiler、platformのlinker、SDKのAPIはtargetの条件に従います。 AndroidもSwiftPMのSwift Buildから扱えるようになり、以前のpost-install scriptへ依存する設定を減らしています。 packageのgraphとplatformのbuild処理の接続を、同じengineで管理する範囲が広がっています。
### package graphとbuild graphからSBOMを生成する
SwiftPMには、Software Bill of Materialsを生成する機能が加わりました。 `swift build --sbom-spec cyclonedx --sbom-spec spdx`で、対応する形式のJSONを生成できます。 productへ使われるcomponentの関係を、packageの名前だけでなく、productのdependencyまで含めて表せます。 manifestとlockfileを読むだけのtoolでは欠けていた、実際のproductが使う関係を得るための追加です([SE-0509](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0509-swift-sboms-via-swiftpm.md))。
package graphは、解決されたdependencyを表します。 platform固有の条件はbuild時に評価されるため、package graphだけでは、target OSで使わないcomponentも残る場合があります。 Swift Buildのbuild graphを使う版は、条件の評価後に必要なcomponentを選びます。 同じlockfileから複数のproductをbuildする場合も、productごとの実際のdependencyを表す情報が増えます。
```sh
swift build --jobs 2 --sbom-spec cyclonedx --sbom-spec spdx
```
Swift 6.4のmacOS版が生成する形式は、CycloneDX 1.7とSPDX 3.0.1です。 schema bundleを含まない配布物では、JSONを生成してもschema validationを省略します。 当該条件で両方の形式に出るwarningは、次の診断です。
```text
Bundle 'SwiftPM_SBOMModel' with schemas not found - skipping SBOM validation
```
SBOMは、再compileのないincremental buildでも生成します。 同じproductを異なるflagでbuildした場合の結果を残すため、file名へtimestampを付けます。 `--product`で対象productを選べますが、`--target`との組み合わせは対象にしません。 packageの部品であるtargetだけへ限定する指定と、配布するproductの構成を表す指定が異なるためです。
`swift package generate-sbom`は、buildせずにpackage graphから生成する入口です。 build graphのplatform条件を使った結果と同じ精度を持つ、という契約ではありません。 build後にdependencyを再解決すると、すでに作ったbinaryとは別のgraphになる可能性もあります。 `--disable-automatic-resolution`は、lockfileが更新を必要とする場合に失敗させ、無条件の再解決を避ける指定です。
## debug情報は、module名から正確なdependency参照へ移る
LLDBでstoredな値を表示する場合と、computed propertyを評価する場合は、必要な処理が異なります。 computed propertyの評価はfunction呼び出しなので、LLDBのexpression evaluatorがSwift codeをcompileして実行します。 型を定義したmoduleを、実際にprogramをbuildしたときと整合する形でimportする必要があります。 6.4は、明示的にbuildしたmoduleのdependencyを追う仕組みを使い、名前だけに基づく探索を減らします([module trackingの変更](https://www.swift.org/blog/module-tracking-in-debug-info/))。
以前は、Darwinのlink時に`-add_ast_path`でbinary Swift moduleを列挙していました。 LinuxやWindowsでは、`-modulewrap`でmoduleの内容をobject fileへ包み、binaryへ含める方法がありました。 多数のmoduleを含むprogramでは、SDKの一部までdebug用の生成物へ入り、fileの大きさが増えます。 同じmodule名にprivate版、公開interface版、異なるtarget版があれば、名前だけで必要な実体を選ぶ問題も残ります。
新しい構成では、binary moduleがSwiftの明示的なdependencyを記録します。 object fileのdebug情報には、自分のmoduleのpathを渡す`-debug-module-path`を使います。 Swift driverはcompile jobへpathを渡すため、SwiftPMやXcodeを使う通常の構成では、利用者がfileごとの指定を追加する必要を減らしています。 Swift frontendを直接呼ぶ独自build systemは、各jobへ同じ情報を渡す必要があります。
6.4以降のexplicit module buildでは、`-modulewrap`や`-add_ast_path`へ依存する処理を外せます。 DarwinのdSYMにはbinary Swift moduleを格納しない構成になります。 binary moduleは作成したcompilerの正確なversionへ依存するため、長期保存するdebug情報と同じ安定した形式ではありませんでした。 moduleへの参照と、変数を読むためのreflection metadataを分け、debug情報を構成します。
function呼び出しなどの複雑なexpressionの評価には、binary moduleを引き続き使います。 同じmachineで直前にbuildしたprogramなら、元の位置やremap先からmoduleを探せます。 moduleを含めないdSYMを保存しただけで、任意の別machineへ必要なmodule一式まで自動で移す構成ではありません。 distributed buildでは、moduleのpathも既存のpath remappingの対象になります。
bridging headerのimportも、正確なexplicit moduleの情報を使います。 以前のLLDBはheaderをsourceからcompileし直していましたが、6.4ではprecompiledなheaderとdependencyを直接importする経路を持ちます。 headerの内容を評価時に再構築する処理を減らし、programをbuildしたときのmoduleへ接続します。 debug情報のサイズ削減と、expression evaluationが使う型の一致を、同じdependency管理の変更で扱っています。
## C/C++との境界でも、pointerとlifetimeを型へ移す
CのAPIをSwiftから実装する場合は、`@c`と`@implementation`を組み合わせる形があります。 C headerの宣言に対する実装を提供する場合と、Swiftから新しくC向けの宣言を生成する場合を分けます。 `@implementation`を付けない`@c`のfunctionでは、compilerが生成headerへ宣言を出します。 pointerとcountを受け取るAPIには、`Span`を使うsafeなwrapperを生成する構成もあります([6.4の相互運用の変更](https://www.swift.org/blog/swift-6.4-released/))。
pointerとcountを別の引数にすると、領域と長さの関係を守る責任が呼び出し側へ残ります。 pointerが指す領域よりcountが大きい場合や、領域の所有者が先に破棄された場合、引数のmachine型が一致しても有効なaccessにはなりません。 `Span`は、領域と要素数、lifetimeの関係をSwift側の型へ移します。 境界で必要な情報をannotationから得られる場合に、安全なaccessを持つwrapperへ接続する構成です([safe interoperabilityの仕様](https://www.swift.org/documentation/cxx-interop/safe-interop/))。
C++20の`std::span`と、Swiftの`Span`の間にもbridgeがあります。 同じ連続領域を扱うviewを、手動でpointerとcountへ分解し直す処理を減らします。 viewは領域を所有するcontainerではないため、渡した`std::span`や`Span`だけを、元のstorageのlifetimeより長く保持できる意味ではありません。 返り値が入力や`this`に依存する場合は、依存を表すlifetimeの情報が必要です。
C++のtypeが所有する値と、内部の値への参照を返すmethodでは、Swift側が持つlifetimeの関係が異なります。 参照を返すmethodから得たviewは、元のC++ objectのmutationや破棄に影響されます。 safe interoperabilityは、条件を記述できるAPIをsafeな形へimportし、根拠を持たない参照を無条件に安全と扱う仕組みではありません。 sourceのannotationと生成するwrapperが、境界で維持する契約になります。
module名が型名で隠れる問題には、`ModuleName::Declaration`というmodule selectorがあります。 `ModuleName.Type`では、`ModuleName`自体が同名の型へ解決される場合がありますが、`::`はmoduleを選ぶ構文です。 SE-0491の実装版はSwift 6.3なので、6.4で初めて使える機能とは数えません。 6.4の公開紹介にも含まれる機能ですが、既存の6.3 codeから移行する際の新規構文とは異なります([SE-0491の採用版と名前解決](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0491-module-selectors.md))。
## Java、WebAssembly、Androidの更新
Swift/Java interopは、asyncやthrowingなfunctionを、protocolとcallbackのwrapperから呼ぶ対応を広げています。 普通の直接呼び出しだけでなく、interface越しに呼び出すmethodのbridgeも対象になります。 closureとJavaの`Runnable`の自動対応、variadic parameterのimport、Java recordへの対応も加わりました。 言語ごとのcallableな値やrecordを、生成するwrapperの公開型へ対応させる変更です([Swift/Java interopの更新](https://www.swift.org/blog/swift-6.4-released/))。
WebAssemblyでは、JavaScriptKitを使うbridgeの実装が更新されています。 Swift codeをWasmへcompileする工程と、JavaScriptの値やoperationをWasm側から呼ぶ境界は別に存在します。 6.4のWasm SDKはSwift.orgの配布から追加でき、Foundationの`FileManager`もWASIでの対応が改善されています。 `FileManager`の追加は、WASIのfile system interfaceが対象です([WasmとWASIの変更](https://www.swift.org/blog/swift-6.4-released/))。
Android向けのSwift SDKは、LTSのNDK 30を使う構成になりました。 Swiftのruntime libraryと、既定のNDKを使うpackageへ、Androidのavailability attributeを提供します。 APIが存在するAndroidの版を表す情報と、host上でcompilerを実行できる条件は異なります。 SwiftPMのSwift BuildがAndroidを扱う変更も含め、target SDKの情報をbuildとimportへ伝える範囲を広げています([Android SDKの変更](https://www.swift.org/blog/swift-6.4-released/))。
## Embedded Swiftは、existentialをclass以外へ広げる
Embedded Swiftは、専用のcompile modeでcode sizeとruntimeの使用量を抑える構成です。 6.4では、`AnyObject`に制約したclassのexistentialだけでなく、一般の`any P`と`Any`を扱えます。 structや整数などをprotocolのexistentialへ格納し、protocolのmethodを呼ぶ表現を広げています。 異なる具体型を共通のprotocolとして扱うcontainerも、使用できる範囲へ入ります([Embedded Swift 6.4の詳細](https://www.swift.org/blog/embedded-swift-improvements-coming-in-swift-6.4/))。
ただし、Embedded Swiftのgenericsは、最終的に具体型へspecialiseするcompile modelです。 `any P`としてしか具体型を知らない値へ、任意のgeneric methodを適用する操作まで同時に許可しません。 公式の例でも、existentialの値へgenericなinstance methodを適用する呼び出しは拒否されます。 protocolのnon-genericなmethodを動的に呼ぶ性質と、未知の型を使って新たなgenericな実装を具体化する性質が異なります。
`throws`の対応も、existentialの追加へつながります。 従来のEmbedded Swiftは、`throws(ParsingError)`のような特定の型を投げる書き方を扱っていました。 型を限定しない`throws`は`any Error`を投げる意味なので、一般のexistentialへ対応した結果、使用できるようになります。 任意のerrorをboxへ包む場合にはheap allocationが生じ得るため、typed throwsと同じ領域使用になる契約ではありません。
metatypeも、限定した型argumentの用途から、値として保持する用途へ広がりました。 `any DefaultInitializable.Type`へ`Int.self`を格納し、protocolのinitializerを呼ぶ形が使えます。 型をcompile時のparameterとして渡すだけでなく、protocolの条件を持つmetatypeの値を扱います。 動的なdispatchやmetadataを使う機能は、実際に使用した箇所のcode sizeと実行経路へ反映されます。
浮動小数点の文字列parseも、Swiftによる再実装によってEmbeddedで利用できます。 concurrency libraryは、throwing taskやthrowing task groupを扱う範囲を広げています。 単に`async`なfunctionを呼べることと、child taskのerrorを回収することを、同じ追加として省略しません。 `EmbeddedRestrictions`のwarning groupは、target全体で、Embedded向けの制約に関係する診断を選ぶ入口です。
## Swift TestingとXCTestの失敗を取りこぼさない
ST-0021は、Swift TestingとCorelibs XCTestの相互運用を定めています。 以前は、Swift Testingのtestから既存の`XCTAssert` helperを呼ぶと、assertionの失敗を報告しない場合がありました。 移行後のtestが成功して見えても、検査の失敗が別framework側へ消える問題です。 6.4では、対応するassertionの失敗をtestのissueとして伝える処理が加わります([ST-0021](https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0021-targeted-interoperability-swift-testing-and-xctest.md))。
反対方向でも、XCTestのtestから呼ぶ`#expect`や`#require`などを、対応する失敗として扱います。 ただし、両frameworkのすべてのAPIを同じ意味へ変換する設計ではありません。 たとえばSwift Testingで`XCTSkip`を投げると失敗として現れるため、静かに失敗を失うassertionと同じ移行対象にはしていません。 `XCTestExpectation`や`XCTWaiter`も、Swift concurrencyのcontextへ無条件に持ち込む対象ではありません。
`CustomTestReflectable`は、期待が失敗したときに表示する値の情報を調整するprotocolです。 通常のdebug表示をprogram全体で変える代わりに、testのdiagnosticに必要な表示を提供します。 大きいdomain objectの内部全体を出すより、比較している値の意味へ沿った情報を出す入口になります。 表示する内容の変更と、期待を評価して成功・失敗を決める処理は分離されています([ST-0022](https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0022-customtestreflectable.md))。
test caseごとの反復も加わりました。 suite全体を単純に同じ回数だけ再実行する代わりに、個別caseの反復条件を扱います。 6.4.1では、warningやknown issueをtest failureと誤って扱い、反復方針へ影響させる問題が修正されました。 失敗を再実行する条件を、報告されたissueの種類へ合わせる修正です([ST-0024](https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0024-per-test-case-repetitions.md)、[反復判定のPR #1924](https://github.com/swiftlang/swift-testing/pull/1924))。
Observationでは、fine-grainedな変更の追跡とcontinuousな通知を扱うAPIが増えています。 1回の変更後に追跡を張り直す処理に加え、値の変化を継続して受け取る処理を表します。 FoundationのProgressManagerにも、Swift concurrencyのasync/awaitへ接続するAPIが追加されています。 非同期の処理と進捗・変更の通知を、callbackだけに限定せず扱う範囲の追加です([SE-0506](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0506-advanced-observation-tracking.md)、[SF-0023](https://github.com/swiftlang/swift-foundation/blob/main/Proposals/0023-progress-manager.md))。
## 6.4.1で修正する生成codeとruntimeの経路
6.4.1では、pointerを通じたlocal variableへの書き込みを、alias analysisが誤って削除するmiscompileを修正しました。 `MutableSpan`のようなviewから値を書いた場合も、元のlocal variableと同じstorageへaccessしている関係が必要です。 書き込み先を別の領域だと誤認すると、後続の読み取りへ必要な値を、生成codeへ残せない場合があります。 sourceの所有検査とは別に、optimizerが表現するaliasの関係を修正しています([optimizerのPR #92631](https://github.com/swiftlang/swift/pull/92631))。
`-Osize`とcross-module optimizationで、複数の`[weak self]` closureがSIL verificationの失敗を起こす問題も修正されました。 LoopInvariantCodeMotionがassignmentを誤った位置へ動かし、release buildのcompilerを失敗させる問題も対象です。 最適化を有効にしたcompileで、closureの表現や代入の位置を変える経路へ関わります。 構文を新しく採用したsourceだけに限定する修正ではありません([weak captureのPR #92707](https://github.com/swiftlang/swift/pull/92707)、[代入の移動に関するPR #92506](https://github.com/swiftlang/swift/pull/92506))。
Linuxのbacktracerには、libcのsymbolがinstallされているときのDWARF parseでcrashする問題の修正があります。 SwiftPMでは、PubGrubがdecision nodeを修復する際の無限loopと、既定のpackage traitの扱いによる解決失敗を修正しました。 FoundationではDTDやnamespace nodeのproperty、`URLSession`が送る`Connection: keep-alive` headerの扱いを更新しています。 Swift Testingには、exit testが非有限の浮動小数点値をcaptureする対応も入っています([6.4.1の修正範囲](https://forums.swift.org/t/announcing-swift-6-4-1/90029))。
6.4の新しいcleanupや所有のAPIは、sourceに表す契約を増やします。 6.4.1のoptimizerやdependency resolverの修正は、同じ契約を持つsourceをbuildする実装へ反映されます。 noncopyableなcollection、lifetime付きのview、非同期の終了処理を採用したprogramでは、型検査と生成codeの両方が動作の条件になります。
### OCaml 5.5詳解——module依存のfunction、polymorphicな引数、GCと配布の変更
URL: https://www.sakashita.page/ocaml-5-5-release-notes-and-analysis/
Last updated: 2026-10-25T12:00:01.000Z
OCaml 5.5.0は2026年6月19日、5.5.1は9月5日、5.5.2は10月23日に公開されました。 5.5.0で増えた言語機能を中心に、5.5.2までのcompilerとruntimeの修正も扱います。 コード例の対象はmacOS/arm64のOCaml 5.5.0です([5.5.0](https://ocaml.org/releases/5.5.0)、[5.5.1](https://ocaml.org/releases/5.5.1)、[5.5.2の公開記録](https://ocaml.org/releases/5.5.2))。
5.5では、moduleの型を後続の引数や返り値へ残すfunctionを書けます。 functionの引数へ明示的なpolymorphic typeを付ける構文も加わりました。 以前はfunctorや補助recordへ分離していた処理を、通常のfunctionに近い呼び出しで表せます。 同時に、標準libraryの文字列処理、整数演算、並行初期化、compilerの再配置にも変更があります([5.5.0の変更履歴](https://ocaml.org/releases/5.5.0))。
## moduleが持つ型を、functionの型へ残す
値をまとめる処理は、初期値と結合operationを引数に取れば、整数と文字列の両方に使えます。 ただし、初期値の型とoperationの引数型は一致する必要があります。 OCamlのmoduleなら、型`t`、初期値`empty`、結合する`append`を同じsignatureへまとめられます。 5.5のmodule-dependent functionでは、受け取ったmoduleの`t`を、後続の値の型として使えます([module-dependent functionの仕様](https://ocaml.org/manual/5.5/firstclassmodules.html))。
```ocaml
module type Monoid = sig
type t
val empty : t
val append : t -> t -> t
end
let combine (module M : Monoid) (values : M.t list) : M.t =
List.fold_left M.append M.empty values
```
`combine`の最初の引数は、`Monoid`に合うmoduleです。 次の`values`は、渡したmoduleの`M.t`を要素とするlistになります。 返り値も同じ`M.t`です。 moduleが提供するoperationだけを取り出すのではなく、operationと値の型の関係を、functionの公開型へ保持しています。
たとえば整数の加算と文字列の連結は、同じsignatureの別実装にできます。 整数側では`t = int`、文字列側では`t = string`を使います。 `empty`は加算なら0、連結なら空文字列です。 `append`を同じ名前で公開していても、渡せる値の型はmoduleごとに異なります。
```ocaml
module Sum = struct
type t = int
let empty = 0
let append = Int.add
end
module Text = struct
type t = string
let empty = ""
let append = String.cat
end
let total = combine (module Sum) [1; 2; 3] (* => 6 *)
let joined = combine (module Text) ["a"; "b"; "c"] (* => "abc" *)
```
`Sum`を渡した呼び出しでは、`M.t`を`Sum.t`、さらに`int`へ対応させています。 `Text`の呼び出しでは、同じ位置が`string`になります。 functionのbodyを2個書くことなく、moduleの型とoperationを組にしたまま呼び出せています。

Sumのtはint、Textのtはstringで、同じcombineの入力listと返り値の型へ反映する図
### first-class moduleとの違いは、返り値側の依存
first-class module自体は5.5の新機能ではありません。 以前から、`(module S)`型の値へmoduleを包み、listやhash tableへ保存できました。 5.5で加わった型は、`(module M : S) -> t`のようにmodule parameterへ名前を付け、右辺の`t`から参照する形です。 単にmoduleを値として渡せる性質へ、型の依存を追加しています([first-class moduleと依存する型の関係](https://ocaml.org/manual/5.5/firstclassmodules.html))。
以前のfirst-class moduleを使う書き方では、`M.t`をfunctionの外へ逃がさない条件が必要でした。 たとえば局所的に取り出したmoduleの型を、通常の返り値へ無条件に露出できません。 呼び出し元から正体を参照できない型が、返り値だけに残ってしまうためです。 5.5の型では、最初のmodule parameterを公開型で束縛し、後続の型が参照する対象を明示できます。
単純な型なら、従来も`(type a)`と`with type t = a`で関係を宣言できました。 `M.t`を局所型`a`へ等置し、listの要素と返り値を`a`として書く方法です。 5.5では、型の関係を宣言するためだけの局所型と等式を減らせます。 さらに、型parameterを取る`'a M.t`にも依存できるため、単純な`t`の書き換えだけでは届かなかった抽象化を扱えます([modular explicitsの実装PR #13275](https://github.com/ocaml/ocaml/pull/13275))。
`'a M.t`を使う例は、任意のmonadを渡してlistを順に処理するfunctionです。 signatureへ`type 'a t`、`return`、`bind`をまとめれば、結果を包む型constructorをmodule側で選べます。 後続のcallbackは`'a -> 'b M.t`、最終結果は`'b list M.t`になります。 `M.t`は要素型を受け取る型constructorなので、`(type a)`だけで全体を置き換える構成とは異なります。
functorでも、moduleの型に依存する処理を表せます。 ただしfunctorの適用結果はmoduleなので、functionを使うために結果moduleを束縛する段階が増えます。 返す内容が少数のfunctionだけなら、module-dependent functionでは呼び出しの式へ直接moduleを渡せます。 新しい型や複数の公開値をまとめて生成する用途では、functorが持つmodule単位の結果も引き続き表現上の違いとして残ります。
### 実行時に選んだpackageを、無条件には渡せない
module-dependent functionへは、staticなmodule argumentを渡します。 `combine (module Sum)`では、適用した場所で`Sum`というmoduleを参照できます。 一方、`(module Monoid)`型へ包んで変数に保存すると、packageの内部型を隠した状態になります。 同じ実装を包んでいても、裸のpackage変数を渡す操作と、module式を渡す操作は型検査の条件が異なります。
```ocaml
let chosen : (module Monoid) = (module Sum)
let result = combine chosen [1; 2; 3]
```
5.5.0では、当該呼び出しは型検査でerrorになります。 診断は、期待する通常の`(module Monoid) -> 'a`と、依存する`(module M : Monoid) -> M.t list -> M.t`を示します。 後者の`M`がscopeの外へ逃げるため、staticなmodule argumentが必要です。 packageを選ぶruntimeの処理だけで、隠れた型を後続の整数listへ一致させられるわけではありません。
実行時の選択が必要なら、packageに残す型の等式を先に決める方法があります。 たとえば`(module Monoid with type t = int)`なら、選ぶ実装の`t`を整数へ固定できます。 packageを局所moduleとして取り出し、`combine (module M)`へ渡せば、返り値の`M.t`を`int`へ簡約できます。 返り値へ局所moduleの名前を残さず、公開されている整数型へ戻せるためです([dynamic selectionの制約と回避例](https://ocaml.org/manual/5.5/firstclassmodules.html))。
依存が残る型constructorでは、同じ方法で単純に等式を除去できない場合があります。 任意の`'a M.t`へ依存するmonadの例では、型parameterを取らない`t`だけの例より制約が強くなります。 module-dependent functionが追加された結果、すべてのmodule選択が実行時の値へ自由に移せる規則になったわけではありません。 型の依存を利用する呼び出しは、依存先のmoduleを適用位置で確定する必要があります。
複数引数をまとめて適用する式にも、型情報を伝える境界があります。 公式manualの`Fun.id sort (module ISetIncr)`は、最初に`Fun.id`の型を使って引数列を検査するため失敗します。 `(Fun.id sort) (module ISetIncr)`と括弧を加えれば、内側の適用で得た依存する型を、外側のmodule argumentの検査へ使えます。 値の計算順序を変えるための括弧ではなく、型検査へ既知のfunction型を渡す単位を変えています。
## polymorphicなfunctionを、引数として受ける
通常のOCamlのfunction parameterは、body内で使う型が1個へ統一されます。 呼び出し元のfunctionが多くの型で使えても、受け取る側が毎回異なる型で呼べるとは限りません。 5.5では、parameterへ`'a.`を含む明示的なannotationを付け、受け取るfunctionが任意の`'a`へ対応する条件を表せます。 body内で整数とbooleanへ別々に適用できます([polymorphic parametersの実装PR #13806](https://github.com/ocaml/ocaml/pull/13806))。
```ocaml
let use (identity : 'a. 'a -> 'a) : int * bool =
identity 41, identity true
let result = use Fun.id (* => (41, true) *)
```
`identity 41`では`'a`を`int`へ、`identity true`では`bool`へ具体化しています。 受け取ったfunctionの型を1回だけ`int -> int`へ固定せず、使用箇所ごとに具体化する条件を、parameterの型へ書いています。 呼び出し元には、整数専用のfunctionではなく、任意の型の値を同じ型で返せるfunctionが必要です。
annotationを外した`let use identity = identity 41, identity true`では、最初の呼び出しが`identity`の型を整数へ固定します。 5.5.0では、boolean literalの位置で整数型を要求する型の不一致になります。 最初の適用によって、同じparameterの入力型が`int`へ固定されるためです。 新機能の追加後も、annotationのないparameterを無条件にhigher-rankな型へ推論する動作にはなっていません。
型変数を明示していない普通の`'a -> 'a`と、`'a. 'a -> 'a`は、受け取る側で約束する範囲が異なります。 前者の`'a`は周囲の型検査に従って1個の型へ決まります。 後者は、parameter自身が任意の型について成立するという条件です。 整数を受け取って整数を返すfunctionは前者の具体例になれますが、booleanでも呼ばれる後者の条件は満たしません。
### GADTのfieldごとに、異なる型の値を返すcallback
実装PRでは、型付きfieldの記述からrecordを作る例を扱っています。 fieldのGADTは、文字列のfieldを指定すれば`string field`、整数なら`int field`になります。 recordを構築するcallbackには、`'a field -> 'a`という対応が必要です。 同じ構築処理の中で、文字列fieldと整数fieldの両方をcallbackへ渡すため、callback自体をpolymorphicに受け取ります。
以前もpolymorphicなrecord fieldへcallbackを入れれば、同じ型の関係を表せました。 たとえば専用recordのfieldを`get : 'a. 'a field -> 'a`にする方法です。 ただしcallbackを渡すためだけのrecord型と、呼び出し側で包む式が増えます。 5.5ではfunction parameterへ型を直接付け、callbackを専用の値へ包む段階を減らせます([型付きfieldの具体例と従来の代替](https://github.com/ocaml/ocaml/pull/13806))。
parameterのannotationは、function全体の型から伝える書き方も可能です。 たとえば`use : ('a. 'a -> 'a) -> int * bool`のように、公開する型へ条件を置けます。 期待する型がapplicationから伝わる場合もありますが、型情報のない境界ではmonomorphicなparameterと仮定します。 単にcallbackのbodyが複数の型で動くという理由だけで、必要なquantifierの位置をすべて自動で補うわけではありません。
### 普通の`apply`へ渡した場合の型変数のscope
通常の型を持つwrapperを挟むと、polymorphic parameterの量化を維持できない場合があります。 `let apply function_ argument = function_ argument`は、通常の`('a -> 'b) -> 'a -> 'b`として推論されます。 `apply use Fun.id`は、`use`のpolymorphic parameterを普通の型変数へ埋め込もうとするため、型検査でerrorになります。 診断は、universal variableがscopeを逃げることを示しています。
`use Fun.id`が通り、`apply use Fun.id`が通らない理由は、functionの実行内容の差ではありません。 `apply`の型が、受け取るfunctionの引数へhigher-rankな型を許す契約を持たないためです。 公開APIへpolymorphic parameterを加えた場合、経由するwrapperの型も同じ条件を表す必要があります。 普通の型変数1個に、任意のpolymorphic typeを代入できるimpredicativeな型体系へ変わったわけではありません。
module-dependent functionとpolymorphic parameterは、同じ依存を別名で呼ぶ機能でもありません。 前者はmoduleのabstract typeや型constructorを後続の型へ残します。 後者は受け取るfunctionを、使用位置ごとに異なる型へ具体化できる条件です。 module側で提供する型の族と、callback側で量化する型変数を、別の境界へ置ける構成になっています。
## local definitionをtypeやeffectへ広げる
5.5では、`let module`、`let exception`、`let open`に限られていたlocal definitionを広げています。 `let type ... in ...`なら、function内部の処理だけに使う型を、moduleで包まずに定義できます。 型のconstructorも、後続の式を検査するscopeへ加わります。 `Cold`と`Warm`を持つ型も、function内部だけへ定義できます([local definitionの仕様](https://ocaml.org/manual/5.5/expr.html))。
```ocaml
let describe () =
let type state = Cold | Warm in
let current = Warm in
match current with Cold -> "cold" | Warm -> "warm"
let result = describe () (* => "warm" *)
```
`state`の値はfunction内部で分岐に使いますが、返り値は普通の`string`です。 返り値へ局所型の名前を持ち出さず、外から参照できる型の値へ変換しています。 既存の`let module Local = struct type state = ... end in ...`を、型の定義だけが必要な用途で短く書ける構成です。
effectのconstructorをlocalに追加する`let type Effect.t += Yield in ...`も、新しい対象です。 effectを宣言する位置を処理の近くへ置けるため、module全体で公開するconstructorと、局所的なhandlerで使うconstructorを分けられます。 handlerの意味やcontinuationの使用規則を変える機能ではなく、宣言できるstructure itemとscopeの変更です。 effect handlerを定義する構文の追加と、local definitionの一般化は公開された版が異なります([local definitionを一般化したPR #14040](https://github.com/ocaml/ocaml/pull/14040))。
PPXが読むattributeの位置には、互換性へ関わる変更もあります。 `let module%foo[@bar] ... in ...`のattributeは、内側のstructure itemのASTへ付きます。 外側の`let`式へ付けたい場合は、`let%foo[@bar] module ... in ...`と書きます。 `let exception`と`let open`も同じ扱いです。 sourceの表記を保っていても、PPXの読む対象nodeは変わり得ます([attributeの付与先を変えるPR #14009](https://github.com/ocaml/ocaml/pull/14009))。
型をlocalに宣言できることと、local typeを返り値から参照できることは独立しています。 `describe`が文字列へ変換して返す構成なら、返り値の型に`state`を残しません。 局所のconstructorを返すAPIを作りたい場合には、外部から識別できる型の定義やpackageの形が必要です。 scopeを小さくする構文は、型を外へ公開する契約を省略する仕組みにはなりません。
## external typeは、abstract typeの区別へ情報を加える
`type t = external "name"`は、OCamlの通常のdatatypeだけでは表さない外部の値を扱う型です。 FFIを通じてC libraryが提供する値などへ、外部型の名前を付けます。 名前が異なるexternal typeは、型検査で異なる型として区別できます。 一方、同じ名前を付けただけで、別々に宣言したexternal typeが自動で等しくなるわけではありません([external typeの仕様](https://ocaml.org/manual/5.5/externaltypes.html))。
```ocaml
type left = external "report.left"
type right = external "report.right"
let impossible (proof : (left, right) Type.eq) =
match proof with _ -> .
```
`Type.eq`は、2個の型が等しいという証拠を表します。 異なる外部名を持つ`left`と`right`なら、等しいというconstructorが成立する場合を排除できます。 `_ -> .`は到達できないcaseを指定するrefutation caseで、異なるexternal type間の等しさを排除します。 値をunsafe castした実験ではなく、型の非互換性を使ってcaseを除外する型検査の例です。
外部名を外し、単に`type left`と`type right`へ置き換えた宣言では、同じrefutation caseを受け付けません。 当該caseは到達不能だと判定できず、到達し得るconstructorとして`Equal`を表示します。 abstract typeは具体的な定義を隠しているため、宣言の名前が違うだけで、任意のmoduleの境界を越えて不等だとは断定できません。 5.5では、現在のmoduleにあるabstract typeを特別に区別していた従来の挙動も変更されています([external type導入のPR #13712](https://github.com/ocaml/ocaml/pull/13712))。
built-inの`float`や`string`も、異なる外部名を持つ型として扱います。 GADTのindexに使った場合、`float`と`string`が一致するconstructorを除外できる根拠になります。 外部名は、表示するためだけのaliasではなく、型同士を区別する検査へ情報を加えます。 foreign functionを自動生成する機能ではないため、値を生成・解放するFFIの宣言は別に必要です。
型parameterのvarianceは、abstract typeと同様に宣言側で指定します。 指定しなければinvariantです。 external typeの型constructorはinjectiveなので、同じ外部型constructorを適用した結果が等しければ、引数型の等しさも検査へ使えます。 型を区別する外部名、variance、injectivityは、GADTやmoduleの境界で利用できる情報の異なる部分です。
以前のrefutation caseが失敗する場合、warningを無条件に無効化するだけでは到達不能の根拠を回復できません。 外部の値を表す型ならexternal type、OCamlのconstructorを使う型なら公開するdatatypeの構造へ、必要な区別を持たせます。 型の不等性を使って省略していたbranchは、compilerが不等性を知る宣言を持つかが移行条件になります。 同じ見た目のphantom labelでも、abstract typeの名前だけに依存していた構成は、5.5の型検査で異なる結果になり得ます。
## 文字列APIは、byte位置と非重複の一致を扱う
5.5の`String`には、substringの検索、分割、置換が加わりました。 `find_first`や`find_last`は位置を返し、`split_first`や`split_last`は左右の文字列を返します。 `split_all`と`rsplit_all`は、一致したseparatorを使って全体を分割します。 `replace_first`、`replace_last`、`replace_all`は、一致したsubstringを指定した文字列へ置き換えます([Stringの5.5 API](https://ocaml.org/manual/5.5/api/String.html))。
APIが使う位置は、Unicodeの文字数ではなくbyte位置です。 `String.length`はbyte数で、indexは0から長さ未満、positionは末尾を含む0から長さまでになります。 検索結果の値をUnicodeのcode point番号と扱うと、UTF-8で複数byteを使う文字の後から位置がずれます。 空文字列の一致では末尾のpositionも有効なので、返った位置が常に`String.get`へ渡せるindexとは限りません。
separatorを`"::"`とする分割では、空の要素を残すか選べます。
```ocaml
let parts = String.split_all ~sep:"::" "::a::::b::"
(* => [""; "a"; ""; "b"; ""] *)
let nonempty = String.split_all ~sep:"::" ~drop:String.is_empty "::a::::b::"
(* => ["a"; "b"] *)
```
先頭と末尾のseparator、および隣接するseparatorの間を、空の要素として保持しています。 `~drop:String.is_empty`は、同じ分割結果から空の文字列を除きます。
空の要素を落とさない版には、同じseparatorで連結すると元の文字列へ戻る性質があります。 前述の入力でも、空の要素を保つことで、先頭、末尾、連続したseparatorを復元できます。 `drop`を指定して要素を取り除いた版は、同じ復元条件を保持しません。 設定ファイルの空fieldを保持したい処理と、空白区切りのtokenだけが必要な処理では、同じ分割APIでも選ぶoptionが異なります。
### 重なる候補では、検索方向によって分割位置が変わる
`split_all`は、非重複の一致を前から選びます。 入力`"aaa"`とseparator`"aa"`では、先頭の2 byteを一致させ、残りの`"a"`を右側へ残します。 同じ入力を`rsplit_all`へ渡すと、末尾側の`"aa"`を選ぶため、`["a"; ""]`になります。
```ocaml
let forward = String.split_all ~sep:"aa" "aaa" (* => [""; "a"] *)
let reverse = String.rsplit_all ~sep:"aa" "aaa" (* => ["a"; ""] *)
```
両方の一致候補を採用すると、中央の`a`を2回消費してしまいます。 非重複という条件は、候補が重なる場合に、検索方向によって採用する位置が変わる理由です。 返すlistの方向を単純に反転するAPIとして考えると、前述の入力では結果を説明できません。 後ろから一致を選んでも、返すsubstringは元の文字列を再構成する順序を保っています。
置換も、先頭側の非重複の一致を選びます。
```ocaml
let replaced = String.replace_all ~sub:"aa" ~by:"X" "aaa" (* => "Xa" *)
let inserted = String.replace_all ~sub:"" ~by:"-" "ab" (* => "-a-b-" *)
```
重なる2個の候補を両方置換して`"XX"`にする処理ではありません。 検索位置と置換対象の消費範囲が、分割APIと同じ非重複の条件へ接続しています。
空のsubstringには、すべてのpositionへ一致する意味があります。 文字の間だけでなく、先頭と末尾にも置換文字列を挿入します。 空のseparatorによる分割も、先頭と末尾の空要素を含むため、空文字列を通常の不一致として扱う実装とは結果が異なります。
`find_all`と`rfind_all`は、結果の位置をlistへ集めるAPIだけではありません。 一致位置とaccumulatorを受けるfunctionでfoldする形なので、一致数や位置の集約値を直接計算できます。 検索結果を全件保存してから数える中間listを、必ず作る構成にはなりません。 返り値をoptionalな最初の位置にしたい場合と、全一致を処理したい場合で、公開するAPIの型も分かれています。
同じsubstringで多数の文字列を検索するときは、`~sub`や`~sep`を部分適用できます。 公式APIは、固定したsubstringの前処理を再利用する使い方を案内しています。 単発の呼び出しを短くする記法だけでなく、同じ検索条件を使う処理のまとまりを表せます。
## 長さで切るAPIと、条件で切るAPI
`take_first`、`take_last`は、指定したbyte数を先頭または末尾から取り出します。 `drop_first`、`drop_last`は、対応するbyte数を取り除きます。 `cut_first`、`cut_last`は、保持する部分と残りをtupleで返します。 長さが文字列より大きい場合や0以下の場合の結果も定義され、単なる`String.sub`の引数作成とは異なります([大きさに基づく分割](https://ocaml.org/manual/5.5/api/String.html))。
`take_first n s`は、`n`が長さ以上なら`s`全体、0以下なら空文字列です。 `drop_first n s`は、`n`が長さ以上なら空文字列、0以下なら`s`を返します。 `String.sub`なら不正な範囲として例外になる入力も、取り出す量を端へ制限する意味で扱います。 負の長さを不正入力として拒否したいAPIから置き換える場合は、入力の拒否条件まで同じにはなりません。
`take_first_while`は、predicateを満たす連続した先頭部分を返します。 全体から条件に合うbyteを抜き出す`filter`ではないため、途中で条件を満たさないbyteへ到達すると終了します。 `drop_last_while`なら、末尾の連続した該当部分を除きます。 先頭の数字、末尾の区切り文字、接頭部の空白など、境界へ連続する部分を扱うAPIです。
predicateが受け取る値も`char`、すなわちbyteです。 UTF-8の任意の1文字をpredicateへ渡す構成にはならないため、Unicodeの文字種を判定する処理を直接代入できません。 ASCIIの区切りを検出する処理と、Unicodeのcode pointをdecodeする処理では、入力単位が異なります。 5.5の追加は、既存の`String`のbyte列という契約を維持して、切り分けるoperationを増やしています。
## 整数の除算は、丸める方向をAPIで選ぶ
5.5では、`Int`、`Int32`、`Int64`、`Nativeint`へ、丸め方を指定する除算が加わりました。 `fdiv`は負の無限大へ切り下げ、`cdiv`は正の無限大へ切り上げます。 `ediv`と`erem`はEuclidean divisionの商と余りで、0でない除数について余りが非負になります。 従来の`div`と`rem`は、商を0へ向けて丸める処理を維持します([Intの除算API](https://ocaml.org/manual/5.5/api/Int.html))。
入力-7と除数3では、丸める方向によって商と余りが変わります。
```ocaml
let truncated = (Int.div (-7) 3, Int.rem (-7) 3) (* => (-2, -1) *)
let directed = (Int.fdiv (-7) 3, Int.cdiv (-7) 3) (* => (-3, -2) *)
let euclidean = (Int.ediv (-7) 3, Int.erem (-7) 3) (* => (-3, 2) *)
```
`ediv`は-3、`erem`は2なので、`-7 = (-3) * 3 + 2`と、非負の余りという条件が同時に成り立ちます。
正の入力では、0へ向ける除算とfloor divisionの結果が一致するため、負の入力で現れる差を区別できません。 負の座標を固定幅のbucketへ分ける用途では、0付近の区切りが変わります。 たとえば幅3で-1を扱うと、0へ向けた商は0ですが、floorの商は-1です。 正負をまたぐ同じ幅の区間を選ぶ処理には、丸め方が分割規則の一部になります。
Euclideanな余りは、常に商をfloorへ丸めれば得られるとは限りません。 除数が正なら`ediv`はfloor側、負ならceil側へ丸めます。 余りを0以上に保つ条件が基準なので、除数の符号によって商の丸める方向が変わります。 単に`mod`の名前だけを`erem`へ変更する移行では、負の入力で返す値の契約が変わります。
除数0は、新しい除算でも`Division_by_zero`です。 丸め方の追加によって、整数の表現幅が無制限になったわけではありません。 `Int`の演算は`Sys.int_size`の幅に従うため、数式として成立する任意精度の計算と、machine integerの範囲を同一視できません。 新しいAPIが選ぶのは商と余りの規則であり、型が表せる整数全体の範囲は使用したmoduleで決まります。
### bitの個数と、値を表すための幅
bitを数えるAPIも、同じ整数moduleへ追加されています。 `popcount`は1のbit数、`leading_zeros`と`trailing_zeros`は先頭と末尾へ連続する0の数です。 `unsigned_bitsize`と`signed_bitsize`は、値をunsignedまたはtwo's complementのsignedな整数として表すための最小幅です。 bitの個数と必要な表現幅は、同じ値から得る別の性質です([bit countingの定義](https://ocaml.org/manual/5.5/api/Int.html))。
15に対する`popcount`と`unsigned_bitsize`の返り値は、ともに4です。
```ocaml
let counts = (Int.popcount 15, Int.unsigned_bitsize 15, Int.signed_bitsize 15)
(* => (4, 4, 5) *)
```
binary表現の`1111`は、1を4個持ち、unsignedなら4 bitで表せます。 一方、`signed_bitsize`は5です。 正の15をtwo's complementで表すには、負の数と区別する先頭の0が必要なので、4 bitの`1111`だけでは足りません。
負数の`popcount`は、two's complement表現のbitを数えます。 数学上の負の整数へ、符号を付けた絶対値のbit数を数えるAPIではありません。 `Int`と`Int64`で表現幅が異なれば、負数の上位へ並ぶ1の数にも差が生じます。 binary formatを扱う実装では、payloadの幅を`Sys.int_size`へ暗黙に合わせるか、固定幅のmoduleを使うかが、結果の条件になります。
`leading_sign_bits`は、先頭の符号bitを除いて、同じ符号へ連続する上位bitを数えます。 正数なら先頭の0、負数なら先頭の1が対象です。 `signed_bitsize`と足すと表現幅になる関係があるため、符号を維持して幅を縮める処理へ使えます。 0の`trailing_zeros`は幅全体で、無限個の0とする数学的な定義ではなく、有限幅のAPIとして値が決まります。
## Optionとcollectionの追加は、値の有無を組み合わせる
`Option.product`は、両方に値がある場合だけtupleを返します。 片方が`None`なら、返り値も`None`です。 `Option.blend`は異なり、片方だけに値があれば値のある側を返し、両方に値がある場合だけ指定したfunctionで結合します。 同じ2個のoptionを受け取っても、必要とする値の存在条件が異なります([Optionの5.5 API](https://ocaml.org/manual/5.5/api/Option.html))。
たとえば2個の任意設定を組み合わせる処理で、両方を必須にするなら`product`です。 片側の設定だけでも採用し、競合した値を統合したいなら`blend`になります。 `blend`へ渡すfunctionは同じ型の2値を結合するので、異なる型をtupleへまとめる`product`とはsignatureも異なります。 値を失敗として伝播する条件と、欠けた側を補って結合する条件を、別のfunctionへ分けています。
`Option.for_all`と`Option.exists`は、空のoptionへ異なるbooleanを返します。 同じpredicateでも、`None`に対する結果は異なります。
```ocaml
let universal = Option.for_all (fun n -> n > 0) None (* => true *)
let existential = Option.exists (fun n -> n > 0) None (* => false *)
```
`for_all`の条件を破る要素は空のoptionに存在せず、`exists`の条件を満たす要素も存在しないためです。 存在の検査を意図した処理へ`for_all`を使うと、値がない入力まで成功させる意味になります。
`Option.Syntax`にはbinding operatorが加わりました。 `let*`で後続のoptionを返す計算をつなぎ、`and*`などで値の有無を組み合わせる書き方を、moduleのAPIとして使えます。 operatorの見た目だけを導入するのではなく、`None`を後続へ伝えるoperationを選んでいます。 recordのfield名のような普通の名前とは異なり、使用するscopeでoperatorの意味を確定させる構成です。
`List.split_map`は、各要素から2値を返すfunctionを適用し、結果を2個のlistへ分けます。 元のlistを2回mapする代わりに、同じ要素から対応する2値を作る処理をまとめられます。 `List.filter_mapi`では、indexを受ける変換が`None`を返せば要素を落とし、`Some`なら変換後の値を残します。 要素の選択と変換を別の走査へ分離していた処理を、単一のcallbackの返り値へ表す追加です([Listの5.5 API](https://ocaml.org/manual/5.5/api/List.html))。
`Hashtbl.find_and_replace`は、以前の値をoptionで返しながら置き換えます。 `find_and_remove`は、取り出した値を返しながら削除するAPIです。 以前の値の存在を知りたい処理で、検索と更新を別々の呼び出しへ分ける必要を減らします。 通常の`Hashtbl`への同時アクセスを自動で同期するAPIではないため、複数domainから共有する構成の同期条件は別に残ります([Hashtblの追加API](https://ocaml.org/manual/5.5/api/Hashtbl.html))。
`Set.S`と`Map.S`には`is_singleton`が加わりました。 要素数がちょうど1個である条件を、cardinalityの整数を取り出さずに検査できます。 `Dynarray.rev_iter`と`rev_iteri`は、後ろからの走査を公開します。 既存の抽象型へ、新しい走査や条件判定の入口を増やしています([標準libraryの追加記録](https://ocaml.org/releases/5.5.0))。
## Formatは、異なる型の引数を1個の引数列へまとめる
5.5では、`Format.lprintf`や`Format.lfprintf`などが加わりました。 従来の`printf`のように引数を個別に適用する代わりに、異なる型の値を持つ専用の引数列へまとめます。 文字列と整数の引数列を、同じformat stringへ対応付けられます。
```ocaml
let () = Format.lprintf "%s=%d@." ["heterogeneous"; 42]
(* => heterogeneous=42 *)
```
文字列と整数を同じ通常のhomogeneousなlistへ入れる規則になったわけではありません([heterogeneous argument listの仕様](https://ocaml.org/manual/5.5/api/Format.html))。
引数列は、format stringから得る型と対応した`Args.t`です。 先頭の`%s`には文字列、次の`%d`には整数という順序を、型検査で照合できます。 通常のlistが全要素の型を揃える条件と異なり、位置ごとに要求する型を持つ構造です。 角括弧の見た目が同じでも、期待される型を通じて使用するconstructorが決まっています。
従来のcurriedな書式出力では、引数をまだ渡し終えていない式もfunctionとして成立します。 必要な引数の不足を、呼び出しの完了位置として判定するには、周囲で期待する型も関係します。 `lprintf`では引数列の終端が構文に現れるため、format stringと引数列の対応を同じ境界で検査できます。 指定子の不足や余分な引数は、長いapplicationの末尾より、引数列の不一致として対応付けられます。
`lfprintf`は出力先のformatterを受け、`lasprintf`は文字列を返します。 `ldprintf`はformatterを後で受け取る形へ整形処理を遅らせます。 引数列を1個へまとめる追加なので、継続を渡して引数の終端を知る専用のvariantを使わずに、渡す値の区切りを表せます。 formatterのbox、break hint、flushの規則は、対応する既存APIと同じです。
`@.`はFormatの整形処理を終えて改行する指定です。 通常の文字列の`\n`だけを入れる操作とは、pendingな出力をflushする条件が異なります。 新しいheterogeneousな引数列が追加されても、Formatが出力をqueueへ置いて行分割を判断する仕組みは維持されます。 `@.`は改行とflushを行い、bufferに残る文字列も出力します。
## 複数domainから共有する初期化を`Lazy.Mutexed`で守る
`Lazy.Mutexed`は、複数domainやthreadから使えるmutex付きの遅延計算です。 最初の`force`で初期化を実行し、初期化中に別の`force`が来た場合はmutexでblockします。 初期化が値を返した後は、同じ値を再利用します。 例外を投げた場合も、以降の`force`で同じ例外を投げる契約です([Lazy.MutexedのAPI](https://ocaml.org/manual/5.5/api/Lazy.Mutexed.html))。
```ocaml
let calls = Atomic.make 0
let delayed = Lazy.Mutexed.from_fun (fun () ->
Atomic.incr calls;
42)
let worker = Domain.spawn (fun () -> Lazy.Mutexed.force delayed)
let first = Lazy.Mutexed.force delayed (* => 42 *)
let second = Domain.join worker (* => 42 *)
let count = Atomic.get calls (* => 1 *)
```
2個のdomainが同じ遅延計算を参照していても、bodyを実行した回数は1回です。 counterの加算は`Atomic.incr`で行い、共有する整数へのdata raceを防ぎます。
普通の`Lazy.t`の`force`は、同じ値へ複数domainから並行して到達する用途のmutexを自動で提供しません。 optionalな設定読み込みなどをlibraryの初回利用まで遅らせる処理では、呼び出すdomainを限定できない場合があります。 `Lazy.Mutexed`は、初期化を実行する側と結果を利用する側の同期を、遅延計算の容器へ含めます。 初期化のfunctionが捕捉したほかの共有状態まで、すべてmutexで保護する意味ではありません。
### fiberへyieldする初期化は、同じthreadでmutexへ再入する
`Lazy.Mutexed`が使うのは、標準libraryの`Mutex`です。 user-levelのfiberが初期化中にyieldし、同じthread上の別fiberが同じ遅延計算をforceすると、同じmutexへ再入する場合があります。 別domainで初期化の終了を待つ例と異なり、同じthreadの制御が初期化の途中へ重なっています。 公式APIは、同じthreadで再びforceした場合に`Sys_error`を投げる条件を説明しています。
再帰的に自分自身をforceする場合も、同じthreadのmutex再入になります。 通常の`Lazy.t`が持つ`Lazy.Undefined`の経路と、同じ例外の契約ではありません。 fiberを内部で使う計算なら、schedulerが提供するblockingやmaskingの仕組みと整合する必要があります。 `Lazy.Mutexed`の初期化bodyには、threadのmutexと整合する計算が必要です。
同じmoduleの`from_val`は、初期化済みの値から遅延計算を作ります。 `is_val`は、force済みで結果が値になっている場合に`true`を返します。 例外で終了した計算を、成功した値と同じ状態として報告するAPIではありません。 遅延する初期化の有無、成功した値の有無、複数domainの競合を、公開するoperationで別に扱っています。
## GCは、回収する時期と走査する範囲を変える
major GCには、cycleの最初にsweepだけを進めるphaseが入りました。 以前のcycleで到達不能になったblockを、再利用できる状態へ移すまでの遅れを短くする変更です。 実装PRでは、到達不能になってから再び割り当てられるまでのlatent garbage delayを対象にしています。 heapが同じ大きさでも、使用済みの領域を再利用できる時期が遅ければ、新しい領域への要求が増えるためです([sweep-only phaseのPR #13580](https://github.com/ocaml/ocaml/pull/13580))。
markingは、rootから到達できるobjectを追って、生存するblockへ印を付ける処理です。 sweepingは、生存しないblockの領域を、再割り当てできる側へ回します。 5.5の追加では、cycleの最初から両方を同じ進行条件で扱う代わりに、先にsweepingを進める期間を置きます。 markingとroot markingはcycleの途中から始まるため、新しく割り当てるblockへ付ける色もphaseに応じて切り替わります。
割り当て直後のblockの扱いを変える理由は、実行中のprogramが作る参照と、collectorが追う参照を整合させるためです。 cycleの途中で作った値を、生存の記録がない古い不要blockと同じ条件で回収すると、programが使う値を失います。 実装PRでは、`UNMARKED`で割り当てる段階から`MARKED`で割り当てる段階へ移る構成を説明しています。 単にcollectionを多く呼ぶ変更ではなく、cycleの中で作業を配置する順序と、新規割り当ての状態を合わせています。
小さいheapには、major GCの`Idle` phaseも加わりました。 programの起動直後や、強制したmajor collectionの後から、毎回同じ量の回収作業を開始する必要を減らします。 起動時の小さい生存集合と、継続して大量に割り当てる状態を、同じ開始条件へ押し込まない構成です。 `Idle`から開始し、full major GCの後も`Idle`へ戻す変更が実装されています([Idle phaseのPR #14365](https://github.com/ocaml/ocaml/pull/14365))。
### 深いstackを持つprogramのminor collection
ARM64、POWER、RISC-Vには、stack frameのgenerational scanningが入りました。 minor GCが若いobjectへの参照を見つける際、深いcall stackを毎回同じ量だけ走査する負担を減らします。 若い世代へ関係する部分を追うcollectorに、stack側の世代に関する情報を使う変更です。 heapのyoung generationだけでなく、heapを参照するroot側の走査量も回収時間へ関わります([stack scanningのPR #13574](https://github.com/ocaml/ocaml/pull/13574))。
深い再帰を使うprogramでstackが大きくなる場合、heapの割り当て量が同程度でも、rootを探す作業は増え得ます。 generational scanningは、若いobjectを回収するたびに古いstack領域を全走査する負担へ作用します。 sourceの再帰をtail callへ変換する最適化とは異なり、存在するstack frameをcollectorが読む方法の変更です。 5.5の記録で対象に挙がったarchitectureに関する追加なので、すべてのbackendへ新しい走査方式が同時に入ったという範囲ではありません。
`Gc.stat`のrecordには、`live_stacks_words`が加わりました。 heap外へ確保したstack fragmentとmetadata、cache内のstack fragmentを含む量をword単位で表します。 現在のstackの大きさを表す`stack_size`と、stackのために確保した領域全体は同じ量ではありません。 停止中のcontinuationや再利用するfragmentも関係するため、heapのlive objectだけからstack用の領域を推測する必要を減らします([GC統計のfield定義](https://ocaml.org/manual/5.5/api/Gc.html))。
runtime eventsには、minor heapへ割り当てたword数と、minor GCでpromoteしたword数のcounterが追加されました。 対応するbyte単位のcounterもあり、eventを出したdomainの量として扱います。 全domainの合計へ1個のdomainのcounterを足すと、同じdomainの値を二重に数えます。 5.5.1では、`EV_C_MINOR_ALLOCATED_WORDS`がheaderを含むword数を報告する修正も入りました([runtime counterの追加と5.5.1の単位修正](https://ocaml.org/releases/5.5.1))。
### GC parameterを名前で指定する`Gc.Tweak`
`Gc.Tweak`は、追加するGC parameterへ名前を付ける仕組みです。 従来の`OCAMLRUNPARAM`の1文字の設定に加え、`Xfoo=123`のような名前付きの指定を扱います。 設定できるparameterを追加した場合、runtimeの設定文字列と、`Gc`側のAPIから同じparameterへ接続できます。 公開される`Sys.runtime_parameters`にも、設定内容が現れます([named parameterのPR #14367](https://github.com/ocaml/ocaml/pull/14367))。
名前を付ける変更は、すべての内部定数を安定した一般用途の設定へ昇格する意味ではありません。 実装PRでは、mark stackのpruning thresholdを、仕組みを使う具体的なparameterとして公開しています。 固定した内部値を変更可能にする入口と、一般的な`Gc.control`のfieldとして契約する入口には、対象となる設定の違いがあります。 同じ文字列を使った起動でも、存在しないparameterを使える前提へ広げず、compilerとruntimeの版へ設定名を結び付ける必要があります。
`Gc.Memprof`には、sampling中かを調べる`is_sampling`が加わりました。 current domainのprofileを、全domainのsamplingを止めずに置き換える処理も可能になります。 program全体を単一のprofileの開始・終了へ固定していた境界を、domainごとの操作へ分ける変更です。 sampleの集約先を切り替える操作と、heapのobjectを回収するGC自体の操作は、異なる状態を扱います([Memprofの変更](https://ocaml.org/releases/5.5.0))。
## domainとFFIに関する変更
`Domain.count ()`は、作成済みで終了していないdomainの数を返します。 最初のdomainも数に含みます。 `recommended_domain_count ()`が推奨する最大数に関する情報なのに対し、`count`は実際に生きているdomainの個数です。 CPU数に関する推奨値と、programが保持する並列実行単位を、別のAPIから読めます([DomainのAPI](https://ocaml.org/manual/5.5/api/Domain.html))。
domainが例外で終了した場合には、`Domain.join`が元のbacktraceを保持して例外を投げ直します。 別domainの失敗を受け取った場所だけを記録する構成より、失敗した処理の呼び出し位置を追えます。 親domainでbacktraceの記録を有効にしていた場合、spawnしたdomainも記録するようになります。 並列処理を起動した側の設定と、例外を回収する側の記録が、同じ調査経路へ接続する変更です([backtraceを保持するPR #14363](https://github.com/ocaml/ocaml/pull/14363)、[設定継承のPR #14416](https://github.com/ocaml/ocaml/pull/14416))。
C側から作成したthreadを登録するAPIには、`caml_c_thread_register_in_domain`が加わりました。 domainのunique IDを渡し、domain 0以外へC threadを登録できます。 登録先のdomainは、呼び出し時に動作している必要があります。 単に終了済みのIDを保存しておき、後から同じ番号の実行領域があると仮定する操作ではありません([C thread登録のPR #14275](https://github.com/ocaml/ocaml/pull/14275))。
通常の`caml_c_thread_register`は、domain 0へ登録する入口です。 新しいAPIは、外部libraryが作るthreadを、意図したdomainへ結び付ける情報を受け取ります。 OCamlのruntimeへ登録することと、外部library側の共有状態を保護することは別なので、callbackで触る値の所有と同期はFFI側の契約に残ります。 5.5では、domainの作成やthreadの登録中にまれなerrorが起きる経路のdeadlockやleakにも修正が入っています。
Windowsでは、並行処理のprimitiveをWinAPIで実装する構成になりました。 Winpthreadsへの依存を外し、platformの同期APIへ直接接続します。 同時に`Unix.kill pid Sys.sigkill`の終了codeは、従来の0から`ERROR_PROCESS_ABORTED`の1067へ変わりました。 processの終了理由を終了codeから分類するtoolでは、正常終了と同じ0を期待した判定が変更対象です([WindowsのruntimeとUnix APIの変更](https://ocaml.org/releases/5.5.0))。
## compilerをinstall先から移せる構成
5.5のcompilerはrelocatableになりました。 binaryと標準libraryの配置関係を維持してinstall treeを移せば、元の絶対pathだけへ依存せず動作できます。 単に`ocamlc`のfileを別のdirectoryへコピーする話ではなく、compiler、runtime、標準libraryが相互の位置を求める仕組みの変更です。 標準libraryの場所を、binary自身の位置から相対的に探す構成を持ちます([relocatable compilerのPR #14244](https://github.com/ocaml/ocaml/pull/14244))。
`--with-relative-libdir`は、runtimeとcompilerが標準libraryを相対位置から探すためのbuild設定です。 以前の`caml_standard_library_default`へ埋めた絶対pathだけを前提にしません。 `-set-runtime-default`は、runtimeが使う標準libraryの既定位置を、compilerから上書きするoptionです。 配布するtreeの構成と、生成した実行fileがlibraryを探す設定を、明示的に合わせられます。
`Sys.runtime_executable`は、実行中のruntimeの完全pathを取得する入口です。 bytecodeのprogramでは、programのfileと、動かしているruntimeのbinaryは同じ対象とは限りません。 runtimeが自分自身の場所を知ることで、相対配置のlibraryを探す処理へ接続できます。 sourceの所在を示すpathと、実行中のruntimeの所在を示すpathを混ぜずに扱うための情報です([runtimeのpathを得るPR #13728](https://github.com/ocaml/ocaml/pull/13728))。
### `ld.conf`の相対pathと、runtime ID
`ld.conf`の明示的な相対pathは、設定fileのdirectoryを基準に解釈します。 `.`、`..`、`./...`、`../...`が対象で、起動時のcurrent directoryへ無条件に結び付けません。 既定の`ld.conf`も相対pathを使う構成になります。 install treeを移した場合に、移動前の標準libraryの絶対pathを参照し続ける問題を減らす変更です([相対pathの解釈を変えるPR #14243](https://github.com/ocaml/ocaml/pull/14243))。
設定fileは、`OCAMLLIB`、`CAMLLIB`、標準のlibrary位置にある`ld.conf`を、最初の1個だけでなく複数読み込みます。 CRLFの改行もWindowsとUnixの両方で揃えて処理します。 一方、`CAML_LD_LIBRARY_PATH`の解釈は変更しません。 相対pathの基準を変える設定fileと、既存の環境変数を、同じ移行規則へ一括で置き換える変更ではありません。
runtimeには、configurationとversionの違いを識別するruntime IDも加わりました。 host tripletと組み合わせ、`ocamlrun`やruntimeのDLL、stub libraryのfile名を区別します。 同じmachineへ異なるruntime構成をinstallした場合、同じ短いfile名だけを探索して誤ったstubへ到達する条件を減らします。 利用者向けの従来名はsymlinkで保持し、名前のsuffix付与は`--disable-suffixing`で無効化できます([runtime IDとfile名のPR #14245](https://github.com/ocaml/ocaml/pull/14245))。
bytecodeの起動には、`-launch-method`と`-runtime-search`も加わりました。 前者はinterpreterを起動する方式を指定し、後者は実行fileと同じdirectoryや`PATH`からruntimeを探すheaderを作ります。 単一のhard-codedなpathだけに固定せず、配布したprogramがruntimeへ到達する経路を選べます。 `ocamlobjinfo`も、bytecode実行fileが起動するruntimeを、`RNTM` sectionやshebangから表示できます。
`ocamlc -custom`でlinkした実行fileは、まず実行file自身からbytecodeを読み込みます。 以前の`argv[0]`を先に試す構成を変え、起動時の引数の綴りだけにfileの所在を委ねません。 実行fileの探索、runtimeの探索、libraryの探索は、同じ配布treeでも別の位置解決です。 5.5のrelocationは、必要なfileの組と相対配置を維持した配布へ対応する変更としてまとまっています。
## 5.5.1は型検査、thread切り替え、runtimeの統計を修正する
5.5.1には、module-dependent functionとdependentなfirst-class module typeを組み合わせた型検査の修正があります。 moduleが内部にmodule typeを持ち、後続の引数が内部のsignatureへ依存する場合のscope errorが対象です。 5.5.0で型検査を通った事実だけでは、依存するmodule typeを渡す全構成で正しい検査が行われた根拠にはなりません。 修正は、通常の非依存applicationとして扱う過程で失っていたscopeの条件を戻します([型検査のPR #14982](https://github.com/ocaml/ocaml/pull/14982))。
Windowsのsysthreadには、runtime lockを保持し続けるregressionの修正が入りました。 threadが自発的にyieldしない場合、preemptiveな切り替えが効かず、同じdomainのほかのthreadが動けなくなる問題です。 明示的な`Thread.yield`やblocking sectionだけで切り替わる状態を修正します。 domainを複数にする変更とは別に、同じdomain内でsysthreadがruntimeを使う順序へ関わります([thread切り替えのPR #15029](https://github.com/ocaml/ocaml/pull/15029))。
stack fragmentの内部cacheには、サイズの上限を付ける修正があります。 再利用のためにstackを保持する仕組みでも、保持する量を制限しなければ、必要な計算が終わった後のmemoryが増え続けます。 5.5.0のstack統計は使用量を返す変更で、5.5.1の修正は実際に保持するstackの量を制限します。 liveな値だけを数えたheapの統計では、cacheの確保領域全体を同じ意味で表せません([stack cacheのPR #14940](https://github.com/ocaml/ocaml/pull/14940))。
runtime eventsでは、欠けていた`EV_MINOR_EPHE_CLEAN` constructorが追加されました。 runtime側は以前からphaseを送っていましたが、OCaml側のvariantに対応するconstructorがありませんでした。 利用側が受け取った値をpattern matchすると、定義した範囲外の値になり、crashする経路がありました。 eventを出すruntimeと、decodeした後の値の型を、同じ集合へ揃える修正です([欠けたphaseのPR #14966](https://github.com/ocaml/ocaml/pull/14966))。
parallel buildでは、interface fileを複数のMake ruleが競合して作る経路も修正されています。 記録されるsource pathが変わると、interface digestや後続の生成物、debug情報にも違いが伝播しました。 同じsourceとcompilerを選んでも、並行する生成処理の勝者によって配布物のbyte列が変わる問題です。 5.5.1では、`str`と`unix`の対象fileを担当する生成ruleを揃えています([再現可能なbuildのPR #14989](https://github.com/ocaml/ocaml/pull/14989))。
## 5.5.2で修正された入力と最適化
5.5.2は、使わない結果を持つatomic loadを、dead-code eliminationで削除しないようにします。 普通のpureな値の読み取りなら結果を捨てる処理を除去できても、atomic operationは同期の意味を持ちます。 返り値の使用有無だけでoperation全体を不要と判断すると、thread間の順序へ関わる処理を失います。 削除する最適化の条件を、atomic loadの意味へ合わせる修正です([atomic loadのPR #15045](https://github.com/ocaml/ocaml/pull/15045))。
pattern matchingには、immediate valueのrangeと冗長なpatternを組み合わせた場合のmiscompilationの修正があります。 sourceで書いたbranchの意味を、最適化後の分岐へ正しく移す問題です。 型が合う入力を拒否する診断の変更とは異なり、compileに成功したprogramが選ぶ処理へ影響します。 5.5.2の変更記録では、対象をrangeとredundant patternの特定の組み合わせへ限定しています([pattern compilationのPR #15185](https://github.com/ocaml/ocaml/pull/15185))。
`Unix.map_file`には、data sizeを計算する際のinteger overflowを防ぐ修正があります。 要素数と要素の大きさから必要な領域を求める処理で、積が表現幅を超える入力を扱います。 領域の大きさを過小な整数として扱わない条件を加える変更です。 識別子は[OSEC-2026-22](https://github.com/ocaml/ocaml/pull/15112)で、通常の型として整数を受け取れることと、安全なsize計算が成立することを別に検査します。
`Str.string_partial_match`では、captureしたgroupが有効な範囲を持つかを検査します。 部分的な一致を返す経路で、groupの開始位置と終了位置を無条件に有効と扱わない修正です。 対象の識別子は[OSEC-2026-21](https://github.com/ocaml/ocaml/pull/15113)です。 文字列の一致判定と、一致に付随するcapture情報を後から読む条件を揃えます。
`Marshal.from_channel`、`Marshal.from_string`、`Marshal.from_bytes`には、複数のdemarshalingの修正が入りました。 対象は[OSEC-2026-23](https://github.com/ocaml/ocaml/pull/15115)です。 5.5.1にも全体のdata lengthの計算でoverflowを防ぐ修正があり、5.5.2では別の読み取り経路も修正されています。 文字列やbyte列からOCamlのobjectを構成する処理なので、sourceの型検査だけで外部dataの内部構造が正しいとは決まりません([5.5.1の長さ検査PR #15019](https://github.com/ocaml/ocaml/pull/15019))。
Windowsのcommand quotingには、[OSEC-2026-24](https://github.com/ocaml/ocaml/pull/15133)の修正があります。 program名や引数をcommand lineへ組み立てる処理では、quoteした文字列を次のparserが同じ引数として読む条件が必要です。 単に空白へ引用符を付ける操作と、Windows側の解釈へ整合するescapingは同じ処理ではありません。 5.5.2では、Windowsのquotingに関する複数の経路を対象にしています。
runtime eventsのring bufferには、messageをdecodeする処理を強化する[OSEC-2026-25](https://github.com/ocaml/ocaml/pull/15119)が入っています。 custom eventのcacheを整合した状態に保つ修正は、[OSEC-2026-26](https://github.com/ocaml/ocaml/pull/15177)です。 さらに、ring bufferの半分より大きいuser eventで無限loopへ入る問題も修正されました。 eventを出す側の実行中に、eventを読むlibraryだけ停止する経路も、更新の対象です([大きいeventのPR #15076](https://github.com/ocaml/ocaml/pull/15076))。
5.5.0の新構文を採用するsourceと、5.5.2のruntimeや最適化の修正を受け取る生成物は、移行する場所が異なります。 moduleやcallbackの新しい型はlibraryの公開signatureへ現れ、compilerの最低版を変えます。 size検査、threadの切り替え、分岐の生成といった修正は、同じsourceをcompileして動かす実装へ反映されます。 5.5系列を指定したbuildでも、正確なpatch versionが、生成したprogramと同梱runtimeの条件になります。
### HaskellをGHC 9.14で動かす——specialisation、stage、recordの変更
URL: https://www.sakashita.page/haskell-ghc-9-14-release-notes-and-analysis/
Last updated: 2026-10-25T11:00:00.000Z
GHC 9.14.1は2025年12月19日に公開されました。 2026年10月24日時点の9.14系列では、9.14.2の2番目のrelease candidateが9月17日に発表されています。 以下では9.14.1の公開済み機能を中心に扱い、9.14.2-rc2の修正は末尾で対象を分けて説明します([9.14.1の公開記録](https://www.haskell.org/ghc/blog/20251219-ghc-9.14.1-released.html)、[9.14.2-rc2の公開記録](https://www.haskell.org/ghc/blog/20260917-ghc-9.14.2-rc2-released.html))。
9.14では、`SPECIALISE`に具体的な値を渡して専用の実装を生成できます。 Template Haskellにはstageを指定するimportが加わり、recordの更新処理では`setField`の引数順序が変わります。 型の引数をconstructorへ渡す構文、linearなrecord field、部分的なselectorの診断にも変更があります([GHC 9.14.1の変更履歴](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html))。
## 型だけでなく、値を固定してspecialiseする
従来の`SPECIALISE`は、polymorphicなfunctionと、専用化する型を指定するpragmaでした。 たとえば`Num a => a -> a`を`Int -> Int`へ専用化すると、数値演算に使うdictionaryを具体的なinstanceへ確定できます。 9.14では、functionにtype argumentや通常の引数を適用した式を指定できます。 型を`Int`へ固定し、さらに分岐の入力を`True`へ固定する、といった専用化が可能です([GHC proposal 493](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0493-specialise-expressions.rst))。
```haskell
{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
scale :: forall a. Num a => Bool -> a -> a
scale twice value = if twice then value * 2 else value + 1
{-# INLINE [0] scale #-}
{-# SPECIALISE scale @Int True #-}
{-# SPECIALISE scale @Int False #-}
```
掲載例は、真なら2倍、偽なら1を加えるfunctionです。 `scale @Int True`は型と最初の値を固定しますが、計算する整数は固定しません。 生成する専用実装の入力には、最後の`Int`だけが残ります。 したがって、引数の個数を変えずに型のdictionaryだけを取り除く従来の専用化より、入力を具体化する範囲が広くなっています。
GHC 9.14.1、macOS/arm64、`-O2`での呼び出し結果は、分岐に対応します。
```haskell
main :: IO ()
main = print (scale @Int True 3, scale @Int False 3) -- => (6,4)
```
simplified Coreでは、加算だけを持つ実装と乗算だけを持つ実装に分かれます。 専用実装のbodyには、元の`Bool`を調べる`case`が残っていません。
```text
scale_$sscale
= \ value -> case value of { I# x -> I# (+# x 1#) }
scale_$sscale1
= \ value -> case value of { I# x -> I# (*# x 2#) }
```
Core断片の一時変数は、compilerが付けるsuffixを除いた表記です。 `I#`から取り出した整数に対し、片方は`+#`、片方は`*#`を使っています。 型のdictionaryを毎回参照する処理と、`Bool`の分岐を毎回選ぶ処理を、両方とも専用化の段階で解消した結果です。 元のpolymorphicな`scale`も残るため、専用化しない型や値の呼び出しを失う変更ではありません。
### 専用実装を作る操作と、呼び出しを書き換える規則
`SPECIALISE`の処理は、新しいbodyを作るだけでは完結しません。 呼び出し元が元の`scale`を使い続ければ、専用化したbodyへ処理が移らないためです。 GHCは、元のfunctionへ固定した引数を渡す式を、生成したfunctionへ置き換えるrewrite ruleも作ります。 `scale @Int True`と`scale @Int False`には、値の固定に対応する2本のruleを生成します。
専用化の対象を選ぶのは、実行中の値を検査するdispatcherではありません。 compilerは最適化中、適用済みの型や値をruleへ一致させます。 呼び出し側の`Bool`が実行時まで分からなければ、単に`SPECIALISE`を書いたという理由だけで片方の専用実装へ確定しません。 専用化で消せる分岐は、最適化時点で既知の入力へ対応します([expression specialisationの生成規則](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0493-specialise-expressions.rst#proposed-change-specification))。
再帰するfunctionでも、固定した値を保って再帰する呼び出しへruleを適用できます。 たとえば同じmodeを渡し続けるloopなら、専用bodyの中から同じ専用bodyへ戻る形になります。 modeを毎回選び直す処理を残さず、modeに対応した再帰を生成できる構成です。 一方、再帰の途中でmodeを変更する呼び出しには、変更後の式に一致するruleが必要になります。
`SPECIALISE`へ渡す引数には、具体的なliteralだけでなく、束縛した変数を含む式も使えます。 たとえばtupleの形を固定しながら、中の値を変数として残す指定です。 自由変数を無条件に取り込む構文ではなく、pragma内の`forall`で束縛する変数と、通常のscopeにある名前を区別します。 生成する専用functionのparameterには、固定しなかった値の情報が残ります。
専用化する式は、型検査を通る必要があります。 元のfunctionへ適用できないtype argumentや値を、pragmaの中だけで許可する規則はありません。 `SPECIALISE`の先頭は対象のfunctionであり、任意の局所式に新しい名前を与える構文でもありません。 定義する位置もtop level、class宣言、instance宣言に限られ、`let`や`where`へ配置する仕組みではありません([proposal 493の構文とscope](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0493-specialise-expressions.rst#proposed-change-specification))。
### `INLINE`の段階と、無効な専用化の診断
掲載例は`INLINE [0]`を使っています。 inlineする段階を指定し、functionの適用という形が最適化の途中まで残るようにした例です。 専用化のruleを使う処理と、元のbodyを呼び出し箇所へ展開する処理は、同じ式へ作用します。 先に元のbodyを展開し尽くした場合には、function名へ一致するruleを使う機会がなくなるため、最適化の段階も関係します。
`NOINLINE`を付けると、同じspecialisationを利用できない場合があります。 9.14.1は、`NOINLINE`のfunctionに対するpragmaを無効な専用化として診断します。 診断番号は`GHC-38524`で、対象のwarningは`-Wuseless-specialisations`です。 `-Werror=useless-specialisations`は、同じwarningをerrorとして扱います。
```haskell
scale :: forall a. Num a => Bool -> a -> a
scale twice value = if twice then value * 2 else value + 1
{-# NOINLINE scale #-}
{-# SPECIALISE scale @Int True #-}
-- GHC-38524: -Wuseless-specialisations
```
診断は、専用化を書いたというsource上の事実と、compilerが実際に使える最適化を区別します。 型と値を具体化しない指定や、元のfunctionと同じ一般性を持つ指定も、無効と判断できる対象です。 追加したpragmaを単に受け付けるだけでは、生成するcodeへ効果があるかは分かりません。 9.14では、無効だと判断できた指定を専用のwarningへ分離しています([無効なspecialisationの追加診断](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#compiler))。
複数の型をcommaで並べる旧形式はdeprecatedになりました。 以前の`SPECIALISE f :: Int -> Int, Double -> Double`のような指定は、pragmaを2個に分ける移行対象です。 新しい診断は`-Wdeprecated-pragmas`が制御し、既定のwarningに含まれます。 旧形式の削除予定はGHC 9.18であり、9.14で直ちに受け付けなくなった構文ではありません([旧pragma構文の変更時期](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#language))。
9.14.1の公開時点では、polymorphic specialisationは既定で無効です。 有効化する場合は`-fpolymorphic-specialisation`を指定します。 公開記録では、不正なcode生成に関するregressionを理由に、既定化を見送ったと説明しています。 式に値を渡せる`SPECIALISE`と、polymorphicな形へさらに専用化する最適化は、有効化の条件が異なります([既定値とissue #26329](https://www.haskell.org/ghc/blog/20251219-ghc-9.14.1-released.html))。
## Template Haskellの依存先をstage別に宣言する
Template Haskellでは、compile中にHaskellのcodeを実行し、生成した構文をprogramへ埋め込めます。 通常の実行時に呼ぶfunctionと、compile時のspliceを作るfunctionでは、必要になる段階が異なります。 9.14の`ExplicitLevelImports`は、importする名前を使うstageをsourceへ記述する機能です。 `import splice`は、top-level spliceを評価する段階で必要な依存先を示します([Explicit Level Importsのproposal](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0682-explicit-level-imports.rst))。
例では、別moduleの`makeAnswer`が整数42の式を作ります。 `StageMain`は、`makeAnswer`を通常のimportではなく、`splice`を付けて読み込みます。 `$(makeAnswer)`をcompile中に評価し、得られた式を`answer`の定義へ入れます。 実行するprogramが表示する値は42ですが、`makeAnswer`を呼ぶのはprogramの実行時ではありません。
```haskell
{-# LANGUAGE TemplateHaskellQuotes #-}
module StageHelper (makeAnswer) where
import Language.Haskell.TH (Exp, Q)
makeAnswer :: Q Exp
makeAnswer = [|42 :: Int|]
```
```haskell
{-# LANGUAGE ExplicitLevelImports, TemplateHaskell #-}
module Main (main) where
import splice StageHelper (makeAnswer)
answer :: Int
answer = $(makeAnswer)
main :: IO ()
main = print answer -- => 42
```
補助moduleを含むcompileと実行には、次のcommandを使います。
```sh
ghc --make StageMain.hs -o stage-demo
./stage-demo
```
`StageHelper`の式を作る処理、spliceの評価、生成したprogramの実行が、別の段階としてつながっています。 module間の依存を単に実行時の呼び出し関係だけで描くと、compileを完了する前に必要な処理が抜けます。

compile時のsplice依存とtargetで実行するprogramの依存を分けた図
### hostで動く補助codeと、targetで動くprogram
cross-compilationでは、compilerを動かすhostと、完成したprogramを動かすtargetが異なります。 spliceを評価する補助moduleは、compile中にhostで動かなければなりません。 一方、完成したprogramからだけ呼ぶ依存先は、target向けのcodeが必要です。 stageを明示すると、依存先を必要とする理由が、import宣言から区別できるようになります([splice importとcross-compilation](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0682-explicit-level-imports.rst#splice-imports))。
通常のimportだけでは、補助functionをspliceから使うかを、moduleのbodyを読むまで確定できない構成がありました。 splice用だと分かれば、hostで実行するobject codeの準備を、通常の依存と分けて進められます。 targetのlibraryをhostで実行しようとして失敗する構成と、host向けに必要な補助libraryを準備する構成では、build graphが異なります。 新しいimportは、型を名前空間へ加える操作に、stageに関する情報を加えています。
`quote`を付けたimportは、quotation内へ参照を残す側のstageに対応します。 spliceはcodeを作る処理を実行し、quoteは生成するcodeの構造を記述します。 両方に同じ名前が登場しても、値を今使うことと、後で使う参照を構文へ残すことは異なります。 stageの区別は、単にsourceを上から順に実行するという時刻の区分ではなく、生成するcodeの段階です。
明示的なlevelの検査では、通常のmodule bodyをlevel 0、top-level spliceをlevel -1として扱います。 名前がimportされたlevelと、名前を使用するlevelを照合します。 別のlevelでしか使えない名前は、通常のmodule exportへ無条件に再公開できません。 exportはlevel 0の利用者へ名前を渡す入口なので、splice専用の束縛を通常の値として渡す説明では整合しません([levelとexportの規則](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0682-explicit-level-imports.rst#exports))。
### stageは同名のimportを自動で選び分けない
異なるmoduleから同じ名前をimportした場合、使用箇所のstageだけで名前の曖昧さを解消する規則ではありません。 通常の`A.x`とsplice用の`B.x`を両方取り込み、裸の`x`を書けば、名前解決が曖昧になる構成があります。 stageを使って都合のよい方を選ぶのではなく、qualifiedな名前で対象を確定させます。 名前解決の後にstageの条件を検査するため、stageの区別が新しいoverloadの解決規則へ変わるわけではありません([Explicit Level Importsの名前解決](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0682-explicit-level-imports.rst#name-resolution-and-ambiguous-variables))。
type classのinstanceも、利用できるlevelの影響を受けます。 同じclassと型に対し、通常のcode用とsplice用に別のinstanceをimportしても、stageごとの別世界として自由に共存させる仕組みではありません。 instanceの選択は、importしたinstance全体に関する整合性を保ちます。 選んだinstanceを使用するlevelの検査と、instance同士の重複検査は、別の条件として残ります。
9.14の`ExplicitLevelImports`は、Template Haskellの構文木を別の形式へ置き換える機能ではありません。 既存のquotationやspliceが行う生成処理へ、依存を宣言する方法を追加します。 したがって、生成する式の型が不正なら、stageを正しく宣言していてもcompileは成功しません。 stageに適した名前の利用と、生成したprogramの型の成立を、同じ1つの検査へまとめる変更ではありません。
## constructorへrequired type argumentを渡す
`RequiredTypeArguments`では、`forall a ->`で呼び出し時に明示するtype argumentを宣言できます。 従来の`forall a.`では、type inferenceで省略するか、`@Int`のようなtype applicationで補えます。 `forall a ->`の引数は、通常の呼び出しの構文へ現れます。 9.14では、visible forallをGADTのdata constructorにも使えるようになりました([required type argumentとGADT](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/exts/required%5Ftype%5Farguments.html))。
```haskell
{-# LANGUAGE GADTs, RequiredTypeArguments, TypeAbstractions #-}
data Typed a where
Typed :: forall a -> a -> Typed a
unTyped :: Typed a -> a
unTyped (Typed _ value) = value
```
掲載例の`Typed Int 42`は、最初に型`Int`、次に値42を渡します。 `Typed Bool True`なら、型`Bool`と値`True`です。 `unTyped`は、constructorへ渡した値を元の型で返します。
```haskell
main :: IO ()
main = print (unTyped (Typed Int 42), unTyped (Typed Bool True)) -- => (42,True)
```
constructorの結果型は、先頭で渡した型に応じて`Typed Int`または`Typed Bool`になります。
型の引数が通常の値を渡す位置へ書かれていても、実行時に型objectを保存する意味ではありません。 required type argumentはcompile中に消去され、型を使うためのruntime reflectionを自動で作りません。 `Typed Int 42`を「`Int`という値と42という値を2個保存する」と説明すると、型消去後の表現を取り違えます。 明示した型は、後続の値と結果型の関係をcompile中に確定するための入力です。
requiredな型の位置でも、`_`を使ってtype inferenceへ任せられます。 ただし、型の引数がsource上に存在するという構文の条件は残ります。 型の引数を完全に省略した呼び出しと、型の位置を`_`で表した呼び出しは同じ形ではありません。 値42から`Int`を推論できる場面でも、visibleな引数を持つ宣言の適用規則に従います([visibleとinvisibleな型の引数](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/exts/required%5Ftype%5Farguments.html#relation-to-typeapplications))。
### 呼び出す構文と、patternで型を束縛する構文
`TypeApplications`は、既存のpolymorphicなfunctionへ型を明示して適用する拡張です。 `TypeAbstractions`は、definitionやpatternの側で型の束縛を表す拡張です。 9.14では、`ScopedTypeVariables`と`TypeApplications`の組み合わせだけでは、patternにtype applicationを書けなくなりました。 型のpatternを使うsourceでは、`TypeAbstractions`を明示する必要があります([型のpatternに必要な拡張](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#language))。
`ScopedTypeVariables`と`TypeApplications`だけで`identity @a value = value`を宣言すると、GHC-78249を出します。 `@a`はillegal invisible type patternとなり、`TypeAbstractions`の追加が必要です。
```haskell
{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
identity :: forall a. a -> a
identity @a value = value
-- GHC-78249: Illegal invisible type pattern
```
同じ`@`でも、呼び出す側と束縛する側を別の拡張で制御する境界が、compile結果へ現れています。
visible forallは、任意の値へ依存する型をすべて表せる機能へ完成したという意味ではありません。 型の引数を明示する構文と、値を型の計算へ反映する仕組みは異なります。 GHCのuser guideも、required type argumentを、proposal全体の部分的な実装として説明しています。 9.14ではdata constructorを含む利用位置が増え、任意のdependent typeは対象に含みません([RequiredTypeArgumentsの実装範囲](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/exts/required%5Ftype%5Farguments.html#relation-to-pi-types))。
## record更新では、新しい値を先に渡す
`OverloadedRecordUpdate`を使うと、record更新を`setField`と`getField`の呼び出しへ変換できます。 9.14では、`setField`へ渡す引数順序が変わりました。 以前はrecord、新しい値の順で、9.14では新しい値、recordの順です。 `RebindableSyntax`で独自の`setField`を定義するsourceは、signatureと実装の両方が変更対象です([overloaded record updateの定義](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/exts/overloaded%5Frecord%5Fupdate.html))。
```haskell
setField :: forall field record value.
Access field record value => value -> record -> record
```
たとえば`original { count = 9 }`は、field名をtype argumentへ渡し、値9と元のrecordを渡す形になります。 新しい値を先に受けるため、部分適用した`setField @"count" 9`を、recordを更新するfunctionとして扱えます。 古い実装へ引数だけを同じ順で渡し続ければ、recordの位置に9が入り、値の位置へrecordが入ります。 型が異なる一般的な例では、実行時に更新結果を間違える前に、signatureの不一致として拒否されます。
整数だけを持つ`Counter`にも、`count`という仮想的なfieldを公開できます。 内部のconstructorへrecord labelを宣言する代わりに、`Access "count" Counter Int`で読み書きを定義しています。 更新は元の`Counter`を変更せず、新しい値を返します。 更新前の値は1を保持し、更新後の値9を持つ`Counter`とは独立しています。
```haskell
{-# LANGUAGE AllowAmbiguousTypes, DataKinds, FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies, KindSignatures, MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedRecordDot, OverloadedRecordUpdate, RebindableSyntax #-}
{-# LANGUAGE TypeApplications #-}
import GHC.TypeLits (Symbol)
import Prelude
class Access (label :: Symbol) record value | label record -> value where
getField :: record -> value
setField :: value -> record -> record
newtype Counter = Counter Int deriving Show
instance Access "count" Counter Int where
getField (Counter value) = value
setField value _ = Counter value
main :: IO ()
main = do
let original = Counter 1
updated = original {count = 9}
print (original.count, updated.count) -- => (1,9)
```
`OverloadedRecordDot`による参照と、`OverloadedRecordUpdate`による更新は、使うoperationが異なります。 参照は`getField`、更新は`setField`へ変換します。 fieldへアクセスする記法を追加しただけでは、更新を担当するfunctionのsignatureまで自動で用意されません。 9.14時点のoverloaded updateは、利用者が`getField`と`setField`を用意するexperimentalな拡張です。
入れ子の`company { owner.name = value }`では、途中の`owner`を取得し、内側の`name`を更新してから、外側へ更新結果を戻します。 内側の更新だけでなく、外側のrecordへ値を組み戻す呼び出しにも、同じ引数順序が適用されます。 独自の`setField`を持つlibraryでは、単純なfield更新と入れ子の更新が同じ変換規則を使うことになります。 field名を文字列としてruntimeで探索する機能とは異なり、名前はtype applicationとして渡ります([入れ子のrecord更新の変換](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/exts/overloaded%5Frecord%5Fupdate.html))。
### 部分的なselectorは`-Wall`で見つかる
一部のconstructorにしか存在しないfieldのselectorは、すべての値へ適用できるfunctionではありません。 `Circle`だけが`radius`を持ち、`Point`には持たせない型なら、`radius Point`は値を取り出せません。 9.14では、`-Wincomplete-record-selectors`が`-Wall`へ加わりました。 以前と同じ`-Wall -Werror`でも、部分的なselectorを使うsourceがbuildに失敗する範囲が増えます([部分的なrecord selectorの診断](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#language))。
```haskell
data Shape = Circle { radius :: Int } | Point
readRadius :: Shape -> Int
readRadius = radius
```
`-Wall -Werror=incomplete-record-selectors`では、`GHC-17335`をerrorとして報告します。 診断に現れる失敗対象のconstructorは`Point`です。 field名の綴りが誤っているのではなく、受け取る`Shape`の集合に、fieldを持たない値が含まれています。 `readRadius`の宣言を読むだけでは見えないpartialityを、selectorの使用箇所で報告する診断です。
`Maybe Int`を返すfunctionへ変えれば、`Circle n`を`Just n`、`Point`を`Nothing`へ対応付けられます。 必ず半径を持つ型だけを受け取るAPIへ分ける方法もあります。 両方とも、従来失敗していた入力を同じ契約のまま隠す修正ではなく、入力または返り値へ失敗条件を表す変更です。 warningをerrorにしない設定ではcompileを続けられますが、selectorの定義域を広げる効果はありません。
record selectorを単にpattern matchingへ置換しても、`Point`の分岐を省略すればpartialityは残ります。 必要なのは、field名を使わない構文へ変えることだけではなく、すべての入力を処理する返り値の設計です。 `Circle`と`Point`の両方を処理する定義なら、半径を取得できない条件がfunctionのbodyに現れます。 9.14のwarningは、recordの記法の背後へ隠れていた失敗経路を、既存のwarning groupへ加えています。
## linearなrecordに、何度でも使うfieldを混ぜる
`LinearTypes`では、入力を1回消費するfunctionを`a %1 -> b`と書きます。 recordを1回消費してpattern matchingすると、constructorの各引数を使う回数にも条件が伝わります。 9.14では、record fieldへmultiplicityを明示し、linearなfieldとunrestrictedなfieldを同じrecordへ置けます。 すべてのfieldへ一律に同じ使用回数を課す代わりに、constructorのparameterごとの契約を表す追加です([linear record fieldの規則](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/exts/linear%5Ftypes.html))。
```haskell
{-# LANGUAGE DataKinds, LinearTypes #-}
import GHC.Types (Multiplicity (Many))
data Record = Record {count %'Many :: Int, marker :: Char}
duplicateCount :: Record %1 -> (Int, Int, Char)
duplicateCount (Record n c) = (n, n, c)
main :: IO ()
main = print (duplicateCount (Record 7 'x')) -- => (7,7,'x')
```
`duplicateCount`は、受け取ったrecordを1回だけpattern matchingします。 取り出した`n`は結果のtupleへ2回使い、`c`は1回使います。 `count`を`Many`としているため、整数を2回使う実装を受け付けます。
通常のfunction arrowである`->`は、multiplicityが`Many`のarrowに対応します。 `%1 ->`は、引数をlinearに消費する関係です。 recordのconstructorへmultiplicityを書いた場合は、recordを作る際に渡す値と、分解して取り出す値の扱いへ反映します。 外側の`Record %1 -> ...`だけを見て、fieldを含めた全値を同じ回数に固定する説明では、`count`の条件を落とします。
### multiplicityは、record selectorのarrowを自動で変えない
fieldのannotationは、record selectorの型へ同じ形で反映されるわけではありません。 `count`や`marker`のselectorは、通常の`Record -> ...`というarrowを持ちます。 constructorをlinearに分解する操作と、既存のselectorをfunctionとして呼ぶ操作には、別の型が与えられています。 fieldのannotationは、selector経由の所有関係をlinearに変えません([constructorとselectorのmultiplicity](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/exts/linear%5Ftypes.html#data-types))。
`newtype`のfieldには、linearである条件が残ります。 一般の`data`へ許可したunrestricted fieldを、`newtype`にも無条件で書けるという規則ではありません。 `newtype`は元の表現と接続する型なので、通常のdata constructorと同じ選択肢を持つとは扱いません。 9.14の追加構文を共通化して生成するtoolでも、`data`と`newtype`で許される宣言を分ける必要があります。
型を推論する際に、compilerが任意のfunctionへlinear arrowを自動で付ける機能でもありません。 linearなfunctionやmultiplicity-polymorphicなfunctionのsignatureは明示します。 signatureなしのfunctionから、使う回数だけを見て公開APIのarrowを自動で変える説明では、既存のtype inferenceの規則と合いません。 掲載例は、外側のarrowとfieldのmultiplicityを両方sourceへ置いています。
## kindの指定とnamespaceを、必要な拡張へ揃える
`DataKinds`を付けずにpromotedな型をkindの位置へ使う処理は、9.14でerrorになります。 従来の`-Wdata-kinds-tc`でwarningとして残していた入力を、拡張なしでは受け付けなくする変更です。 たとえば長さを型へ含める`Vec`で、parameterのkindを`Nat`へ指定する宣言が該当します。 `KindSignatures`によってkindを書く構文を許可したことと、`Nat`をkindとして利用する条件は別です([DataKindsなしのkind指定](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#language))。
```haskell
{-# LANGUAGE KindSignatures #-}
import GHC.TypeNats (Nat)
data Vec (n :: Nat) a = Empty
```
`DataKinds`がない宣言には、`GHC-68567`を出します。 `Nat`の展開先はillegal kindとなり、kindへ使うには`DataKinds`が必要です。 型のparameterへ`n`を書くことだけが問題なのではなく、`Nat`をkindの位置へ置くための拡張が不足しています。 9.14では、warningの抑制だけで同じ宣言を存続させる段階から、必要な拡張を明示する段階へ変わります。
`ExplicitNamespaces`では、importとexportの名前に`data`を付けられるようになりました。 型の名前とdata constructorの名前が同じ綴りになる場合にも、取り込むnamespaceを明示できます。 従来の`pattern` namespace specifierはdeprecatedとなり、`-Wpattern-namespace-specifier`で報告します。 constructorやpattern synonymを指す名前と、型を指す名前を、同じ綴りだけで曖昧に扱わないための構文です([namespace指定の変更](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#compiler))。
subordinate import listの中で不正な`type`指定を使っていた入力も拒否されます。 親の型へ属する名前を列挙する位置と、独立した型の名前をimportする位置では、許される構文が違います。 namespaceを明示する拡張を有効にしただけで、すべての名前の列挙位置へ同じspecifierを挿入できるわけではありません。 compilerやsource生成toolがimportを書き出す場合は、名前の所属と構文の位置が接続する条件になります。
### `coerce`のtype inferenceと、同じmoduleのtype family
`coerce`の呼び出しでは、`Coercible` constraintからtype variableを決定する推論が改善されています。 型の関係をconstraintに書いてあっても、変数を曖昧だと判断していた入力を扱うための変更です。 値の表現が同じ型同士を接続する`coerce`へ、新しいruntime変換を加える変更ではありません。 受け付けるsourceの型を確定する過程が変わり、既存の`Coercible`関係が推論へ反映される範囲を改善しています([coerceの推論改善](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#language))。
kind checkerも、同じmoduleにあるtype family instanceを、別の宣言のkindを調べるために使う処理を改善しました。 type familyの適用結果がkindの計算へ入る場合、instanceをまだ利用できない状態で検査すると、本来確定できるkindを扱えません。 宣言が同じfileにあるという位置関係だけでなく、型の計算に必要な情報の依存が関係します。 9.14は、複数の既存issueにまたがっていた同じmodule内の処理を修正しています([kind checkerの対象issue群](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#compiler))。
`DeepSubsumption`のhigher-rank typeに関する修正も含まれます。 functionの引数や結果にpolymorphicな型が現れる場合、単純な第1階の型の一致だけでは処理できません。 9.14ではhigher-rankな型を扱う複数の経路を修正していますが、拡張を付けずにすべてのhigher-rankな書き方を許す変更ではありません。 拡張の選択、型のsubsumption、診断するsourceの関係を保った修正です。
quantified constraintの解決では、一部の入力でsolverが無限に処理を続ける問題が修正されました。 constraintを解決する過程で別のquantified constraintを再び要求すると、同じ要求を増やし続ける経路ができます。 型エラーの内容だけではなく、compileが完了するかに影響する修正です。 修正対象は型検査のconstraint solverで、runtimeの評価が終了する性質とは独立しています([quantified constraintの修正](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#compiler))。
## warningとcoverageが扱うsourceの範囲
`-Wrule-lhs-equalities`は、rewrite ruleの左辺でquantifyしたequality constraintを報告します。 以前のcompilerが受け付けていた形式でも、生成されたruleが実際には発火できない構成が対象です。 9.14では該当するruleを落とし、warningによって無効な最適化の指定を見える形にします。 programの型検査を通った事実と、最適化のruleが呼び出しへ一致する事実を、同じ結果として扱わない診断です([RULESのequalityに関する変更](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#compiler))。
`UNPACK`を付けたconstructor fieldをunpackできない場合の診断には、専用の制御名が加わりました。 `-Wunusable-unpack-pragmas`で有効にし、`-Wno-unusable-unpack-pragmas`で無効にできます。 `-Werror=unusable-unpack-pragmas`なら、期待した格納方式へ変換できなかったbuildを失敗にできます。 従来の無条件なwarningを、ほかのwarningと同じ粒度で制御できるようにした変更です。
code coverageを有効にする`-fhpc`では、record fieldを参照する記法の扱いが変わります。 `RecordWildCards`や`NamedFieldPuns`によってfieldを取り出した場合も、生成されたselectorを使う参照と同じようにcoverageへ反映します。 `radius value`と明示しなかったというsourceの違いだけで、fieldの利用を未実行として扱う問題へ対応します。 同じfield参照を、sourceの表記によらずcoverageの記録対象へ揃える変更です([field参照とcoverage](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#compiler))。
warningの有効化とerror化は別の指定です。 `-Wall`は対象のwarning群を選び、`-Werror`は有効なwarningをbuildの失敗へ変えます。 部分的なselectorの診断を`-Wall`へ追加すると、sourceを変更していないlibraryも失敗し始めます。 選択したwarning groupの内容が増えるためです。 warning名を個別に扱う設定なら、変更された診断だけをerror化したり、既存のほかの診断を維持したりできます([warning groupとseverityの指定](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/using-warnings.html))。
## GHCiは複数のhome unitを読み込める
GHCのhome unitは、利用中のprojectでsourceから扱う単位です。 すでにinstallされたpackageを参照するだけの状態と、sourceを変更しながら同じsessionで読み直す状態は異なります。 9.14のGHCiは、複数のhome unitを扱えます。 複数packageから成るprojectで、片方を固定したinstall済みの依存としてしか扱えなかった境界を広げる変更です([GHCiのmultiple home units](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#ghci))。
同名のmoduleでも、所属先のunitは異なり得ます。 複数unitを扱うcompiler APIでは、moduleの綴りだけをkeyにして全projectを1つの表へまとめると、区別する情報を失います。 home packageとunit graphを管理するAPIの変更は、複数のhome unitを扱うGHCiや開発toolにも関わります。 REPLへsourceを読み込む機能と、sourceの所属単位を追うcompiler内部の管理が接続しています。
`:info`は、意味上必要な`@` binderを宣言の表示へ残すようになりました。 表示を短くするために型の束縛を落とすと、見えた宣言を再利用した際に、元と同じkindや束縛順序にならない場合があります。 型を表示する入口では、読みやすさのための省略と、意味を変える省略を分ける必要があります。 9.14は、`@` binderを含む型を扱う際の表示情報を修正しています([GHCiのinfo表示](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/ghci.html#ghci-cmd-:info))。
`scale`の型と式の評価は、GHCiでも表示できます。
```ghci
ghci> :info scale
scale :: Num a => Bool -> a -> a
ghci> scale @Int False 3
4
```
GHCiで式を評価する処理は、`-O2`でnative executable向けのCoreを最適化する処理とは異なります。 `SPECIALISE`で分岐が消える条件は最適化とrewrite ruleにあり、GHCiの返り値が4である条件とは分けて扱います。
### debuggerは`do`のstatementへ停止点を置く
GHCiのdebuggerでは、`do`の各statementがbreakpointの対象になります。 複数の式を組み合わせたsourceでも、実行中のstatementを追う位置が増えます。 functionへの入口に加えて、内部のstatementごとに実行を停止できます。 実行するIOの順序を変更する機能ではなく、停止点の配置をsourceのstatementへ対応させています([GHCi debuggerの変更](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#ghci))。
新しい`:stepout`は、停止中のfunctionやbindingから外へ進むcommandです。 内部の式を1個ずつ追う操作とは異なり、呼び出しの境界から外へ戻るところを選びます。 9.14ではtechnology previewとして追加されているため、GHCiのほかの全commandと同じ実装成熟度を示す記録ではありません。 `:steplocal`にも性能改善が入り、外部debuggerとの統合へ向けたAPIの整理も含まれています。
bytecodeのbreakpoint indexは32 bitになりました。 大きいmoduleへ多数の停止点を付ける場合にも、少ないindex幅を前提とした上限へ早く到達しないための変更です。 multi-threadを扱うdebuggerへ向けた内部refactoringも入っていますが、単にindex幅を増やしたことでthread間の実行関係が完成するわけではありません。
GHCi自身のerrorやwarningにもnumericな診断番号が付きます。 型検査の診断を文字列の全文で分類するtoolでは、表記や文言の変化が識別方法へ影響します。 番号を含む出力を使えば、説明文の綴りと診断の種類を別に扱えます。 compiler本体の診断と、対話的なcommandの診断を、同じmessage本文だけで一括解析する必要を減らす変更です。
## native codeとWebAssemblyで異なる追加
x86向けnative code generatorは、128-bitの整数SIMD operationを追加しました。 `shuffleFloatX4#`と`shuffleDoubleX2#`は、`-mavx`を要求しなくなっています。 浮動小数点vectorのshuffleに使う命令の条件と、整数vectorの演算の追加は、対象のoperationが異なります。 命令の変更対象はx86のnative code generatorで、arm64向けの命令生成は含みません([native code generatorの変更](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#compiler))。
LoongArch向けには、native code generatorの初期対応が入りました。 あるarchitectureのcodeを生成できる段階と、すべてのOS、ABI、package構成で配布物が揃う段階は異なります。 9.14の記録は初期対応であり、既存の全libraryが同じ条件で動くという互換性の保証へ拡大できません。 利用するtarget向けのcompilerとruntimeを持つことが、生成先を選ぶ際の前提になります。
WebAssembly向けのGHCでは、interpreterを通じた評価が追加されています。 GHCiの対話的な評価と、Template Haskellの評価を、Wasm側の構成でも扱う変更です。 `foreign import javascript`も関わるため、browser内のJavaScriptやDOMへ接続する評価を表せます。 完成したWasm moduleを起動するだけでなく、codeを読み込みながら評価する工程へ対応範囲を広げています([Wasm interpreterの追加](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#webassembly-backend))。
GHCは、単一の配布物がすべてのtargetへcodeを生成するcompilerではありません。 通常のnative向け`ghc`へoptionを追加するだけで、同じ配布物から`wasm32-wasi`を生成する構成にはなりません。 Wasm向けのGHCでは、`wasm32-wasi-ghc`などのtargetを含むcommandを使います。 interpreterの追加は、通常のmacOS向けcompilerにWasm出力を切り替えるoptionを加えた変更ではありません。 Wasm用のtoolchainが独立しています([GHCのWasm向け配布とcommand](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/wasm.html))。
Wasm側の修正には、dynamic linkerの`GOT.func`、GHCi初期化時の`setKeepCAFs()`、JSFFIの初期化処理が含まれます。 `GOT.func`の先行宣言を扱う処理は、後から解決するfunction参照の位置へ関わります。 JSFFIの初期化用constructorが利用者の`main`と衝突しない修正もあり、programの入口と補助処理の入口を区別しています。 同じ「Wasm対応」でも、名前の解決、初期化、対話評価に関する変更が別に含まれています。
## runtime linker、共有するthunk、heapの予約
runtime linkerには`--optimistic-linking`が追加されました。 未解決のsymbolがあってもlink処理を継続する指定で、既定では有効になりません。 symbolの実体を自動で生成したり、参照を別のfunctionへ置き換えたりする機能ではありません。 未解決の参照を残して進める動作と、解決済みのprogramを作れた動作には、成立している条件の差があります([optimistic linkingの追加](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#runtime-system))。
RTS optionは、programの通常の引数とは別に処理します。 link時に許可した設定に従い、実行時の`+RTS`から`-RTS`の間をruntime自身が読み取ります。 GHCiやcompilerのoptionと、完成したprogramのruntime optionを混ぜると、指定を読むprocessが変わります。 9.14のlinker optionを使う場合にも、sourceの最適化flagではなく、runtime linkerへ作用する指定として位置付けます([RTS optionの受け渡し](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/runtime%5Fcontrol.html))。
black holeは、別のthreadが評価中のthunkを共有する際の状態に関わります。 複数threadが同じ遅延計算へ到達すると、評価を担当するthreadと、完了を待つthreadをruntimeが調整します。 9.14には、eager black holeの変更を記録する処理と、評価を担当しようと競合するthreadのassertionの修正が入りました。 利用者のsourceに明示的な共有変数がなくても、共有したthunkの評価がruntimeの同期へ接続するためです([black holeの修正issue #26495](https://gitlab.haskell.org/ghc/ghc/-/issues/26495))。
`threadPaused`には、black holeで停止していたthreadへの通知を失う問題の修正があります。 評価中のthunkが完了しても、待機していたthreadを再開する通知を失えば、処理は進みません。 値の計算結果を間違える問題とは異なり、計算を待つ側が再開できるかに影響します。 9.14の修正は、共有する計算の完了と、待っていたthreadへの通知を接続する経路を対象にしています([lost wakeupのissue #26324](https://gitlab.haskell.org/ghc/ghc/-/issues/26324))。
POSIXのheap予約には、OSが低いmemory addressを繰り返し返した場合の無限loopの修正が含まれます。 OSがheap予約へ返す領域と、runtimeが利用できるaddressの条件が合わない場合は、予約要求をやり直します。 やり直しのたびに同じ条件へ戻る構成では、予約処理が完了しません。 programが要求した値の計算を始める前の、領域を確保する処理へ関わる修正です([heap予約のissue #26151](https://gitlab.haskell.org/ghc/ghc/-/issues/26151))。
Windowsのruntime linkerは、DLLのpath文字列を複製してからcacheへ保存します。 一時的な文字列の領域を参照したままcacheへ保存すると、領域の寿命が切れた後にもcacheがpointerを持ち続けます。 後から同じDLLを探す処理がcacheへ到達すると、保存した文字列を安全に読む条件が失われます。 9.14では、cacheの寿命へ合う文字列を保持して、解放後の領域を参照する経路を修正しています([DLL pathのissue #26613](https://gitlab.haskell.org/ghc/ghc/-/issues/26613))。
non-moving GCでは、`WHITEHOLE` closureを走査する処理も修正されました。 objectを移動しないcollectorでも、closureの評価状態が変わる途中の表現を正しく扱う必要があります。 移動しない性質から、並行して変わる状態の扱いまで不要になるわけではありません。 9.14には、collectorが走査する状態と、thunkの評価を調整する状態に関する修正が同じruntimeの変更として含まれています([non-moving GCのissue #26204](https://gitlab.haskell.org/ghc/ghc/-/issues/26204))。
## 同梱libraryと、compiler APIを使うtoolの移行
GHC 9.14.1には`base 4.22.0.0`が同梱されています。 同梱された`base`のversionは、package databaseから取得できます。
```sh
ghc-pkg field base version
# => version: 4.22.0.0
```
`base`はUnicode 17.0.0へ更新され、文字の分類などが参照するUnicodeの版も変わります。 compilerの構文だけを更新したつもりでも、同梱libraryが持つ文字データは従来版のままではありません([baseの更新と同梱version](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#base-library))。
`GHC.Exts`からは、安定しないheapの表現に関する詳細が取り除かれました。 runtimeの内部状態を調べるtoolでは、公開名が残り続けることと、内部表現が安定した契約であることを区別する必要があります。 9.14のlibrary構成では、heapを調べるための`ghc-heap`にも専用の取得functionが追加されています。 closureの情報やpointer argumentを読む操作を、部分的なrecord selectorへの依存から分離するAPIです。
追加された名前には`getClosureInfoTbl_maybe`と`getClosureInfoTbl`があります。 pointer argumentには`getClosurePtrArgs`と`getClosurePtrArgs_maybe`が加わりました。 名前の`maybe`は、取得する情報が存在しない経路を返り値で表す入口です。 内部のclosure全種類に同じfieldがあると仮定してselectorを呼ぶ構成より、取得できない条件をAPIへ含められます([ghc-heapの追加API](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#ghc-heap-library))。
`ghc-prim`は、primitive operationや型を公開するlegacy interfaceという位置付けになりました。 primitive operationと型は、`ghc-experimental`からも公開されます。 primitive名を使うcodeは、compilerのversionへ密接に結び付くため、通常の`base`の安定したAPIと同じ移行範囲にはなりません。 9.14では、compiler内部の機能を公開するpackageの境界を整理しています。
`annotateStack#`は、call stackへ任意のdataを押し込み、後からstack traceをdecodeする処理で取り出すためのprimopです。 単に現在のfunction名を文字列として保存する機能より、利用者が持つdataをstack上の文脈へ関連付ける入口になります。 追加したdataを後から読む処理まで、stackをdecodeする側の経路として必要です。 primopを呼ぶだけで、すべての例外表示へ任意のdataが自動で整形されるというAPIではありません([annotateStack#のissue #26218](https://gitlab.haskell.org/ghc/ghc/-/issues/26218))。
`GHC.RTS.Flags`と`GHC.Stats`に対応するexperimentalなmoduleも追加されています。 公開名は`GHC.RTS.Flags.Experimental`と`GHC.Stats.Experimental`です。 `base`側にも従来のmoduleは存在しますが、packageを分割する計画に伴い、experimental側へ移す対象として説明されています。 module名が変わる移行と、runtime統計の値を読む処理の意味が変わる移行を、同じ変更としては扱っていません([ghc-experimentalの公開module](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#ghc-experimental-library))。
### HomePackageTableの更新は`IO`へ移る
compilerをlibraryとして使うtoolでは、HomePackageTableの変更が直接影響します。 9.14の表は`IORef`を使う構成になり、作成や要素の追加を含む多くのoperationが`IO`へ移りました。 以前のpureな値の組み立てとして呼んでいた処理を、単にimport元だけ変えて使う移行にはなりません。 tableの状態を更新する作用を、呼び出すfunctionの型と実行順序へ反映する必要があります([HPTとHUGのAPI変更](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#ghc-library))。
`emptyHomePackageTable`や`addHomeModInfoToHpt`も、`IO`で扱う名前として説明されています。 表へ要素を足す処理が作用になれば、表を共有するsessionが持つ状態を、pureなsnapshotと同じようには扱えません。 呼び出す順序、参照する表、更新後に読む処理の位置が、tool側の実装へ現れます。 変更の理由には、従来の表の構成で起きていたmemory leakを防ぐことが挙げられています。
`UnitEnvGraph`と関連operationのimport元は、`GHC.Unit.Home.Graph`へ移りました。 `hugElts`は削除され、unitのkeyが必要なら`allUnits`を使う構成です。 要素を処理する用途では、`traverse`や`unitEnv_foldWithKey`を使う経路になります。 削除した名前を1つの新しい名前へ機械的に置換するだけではなく、keyを読む用途と、値を処理する用途で対応先が分かれます。
診断を包む`UnknownDiagnostic`には、hintの型を表すtype argumentと、hintを包む値の引数が増えました。 error本文だけを包むtoolの実装では、inner diagnosticが持つhintの情報も扱う必要があります。 syntax treeでは、parallel statementのblockを表すlistが`NonEmpty`へ変わる変更もあります。 空を許していた容器から空を作れない容器へ型が変わるため、ASTを構築するtoolでは、生成する要素数の条件も更新対象です。
Template Haskellでは、`SpecialiseP`に代わる`SpecialiseEP`が追加されました。 expressionを先頭に持つpragmaへ対応する変更であり、従来のfunction名だけを包む形より、表す構文が広くなります。 helperも`pragSpecED`と`pragSpecInlED`へ移ります。 sourceで新しい`SPECIALISE`を書ける変更と、sourceを生成・解析するlibraryの表現の変更が、同じ機能追加の両側へ反映されています([template-haskellの変更](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#template-haskell-library))。
同梱versionでは、`template-haskell`が2.24.0.0、`ghc-prim`が0.13.1です。 `ghc-experimental`と`ghc-internal`は9.1401.0になっています。 `Cabal`と`Cabal-syntax`は3.16.0.0で、同梱するlibraryのversionと、利用者が別にinstallした`cabal` commandのversionは同じ対象ではありません。 同梱packageのversionは、compilerの配布物に対応します。 dependencyを解決するCabalのversionも、別の入力としてbuildへ関わります([同梱package一覧](https://downloads.haskell.org/ghc/9.14.1/docs/users%5Fguide/9.14.1-notes.html#included-libraries))。
## 9.14.2-rc2で追加修正される経路
9.14.2-rc2は、9.14.1に新しい言語機能を一括して追加する別系列のreleaseではありません。 9.14.2へ向けた2番目のcandidateで、specialiser、demand analyser、runtimeなどの修正を含みます。 2026年9月17日の公開記録では、不正な結果、crash、停止しない処理へつながる複数のmiscompilationの修正を挙げています。 対象は式の専用化とruntimeの実行経路で、9.14.1に対する型検査の成功だけでは当該不具合を除けません([9.14.2-rc2の修正記録](https://www.haskell.org/ghc/blog/20260917-ghc-9.14.2-rc2-released.html))。
specialiserとdemand analyserの不具合は、同じHaskellの式から生成するcodeの意味へ関わります。 sourceの型が正しくても、最適化したbodyが元の評価と同じ結果を返さなければ、programの動作は壊れます。 型検査へ新しい制約を追加する修正と、不正な最適化を修正する変更は、失敗する段階が異なります。 candidateの公開記録では、複数のissueを生成結果の誤りに関する修正としてまとめています。
parallel GCには、必要なmemory barrierが不足していた問題の修正があります。 AArch64のような弱いmemory orderingのarchitectureでは、別threadが読み取れる書き込みの順序が、runtimeの前提と一致しない場合があります。 collectorが正しい状態を読むための同期が不足すれば、heapの破損やcrashにつながります。 修正は、collector間のmemory orderingへ必要なbarrierを追加します。 適用先は型検査の規則ではなく、runtimeの同期処理です([memory barrierのissue #27477](https://gitlab.haskell.org/ghc/ghc/-/issues/27477))。
9.14.1の新構文を使うlibraryでは、必要な拡張、warningのgroup、compiler内部のAPIを依存の境界として持ちます。 9.14.2へ向けた修正は、同じsourceを最適化して動かす際のcode生成やruntimeの経路へ重なります。 したがって、同じ9.14系列でも、sourceの移行で変える宣言と、compilerを更新して受け取る修正は別の作業です。 式の専用化、stage別の依存、recordの型と更新規則をsourceへ反映したうえで、利用したcompilerの正確な版が生成物の条件になります。
### Kotlin 2.4.20のJVM、coroutine、Wasmの変更
URL: https://www.sakashita.page/kotlin-2-4-20-release-notes-and-analysis/
Last updated: 2026-10-25T10:00:00.000Z
Kotlin 2.4.20は2026年9月7日に公開されました。 JVM 21以降の`when`に対する`invokedynamic`の利用がstableになり、既定で有効になります。 標準libraryの追加APIにはexperimentalの機能もあり、個別のopt-inが必要です([Kotlin 2.4.20の公式リリースノート](https://kotlinlang.org/docs/whatsnew2420.html))。
## `when`の型判定をJVMのtype switchへ渡す
複数の型を調べる`when`では、対象の値に`instanceof`を繰り返すbytecodeを生成できます。 2.4.20は条件を満たす`when`について、JVMの`SwitchBootstraps.typeSwitch()`を使う`invokedynamic`を生成します。 対象の値と型の組み合わせをtype switchで選び、選ばれた分岐へ移る形です([JVMでのbytecode生成](https://kotlinlang.org/docs/control-flow.html#bytecode-generation-on-the-jvm))。
```kotlin
sealed interface DeepEvent
data class DataEvent(val count: Int) : DeepEvent
data object EndEvent : DeepEvent
fun eligible(event: DeepEvent): Int = when (event) {
is DataEvent -> event.count
is EndEvent -> -1
}
```
Kotlin 2.4.20で`-jvm-target 21`を指定すると、`eligible`へ`invokedynamic`と`tableswitch`を生成します。 分岐が返す値は、元の`when`と同じです。
```kotlin
println(eligible(DataEvent(2))) // => 2
println(eligible(EndEvent)) // => -1
```
適用には、`else`以外の条件が`is`か`null`の判定であることなどの条件があります。 guardの`if`を含まず、`else`以外に少なくとも2つの条件が必要です。 mutable collectionやfunction typeのように、単純な型判定だけでは区別できない型も対象外です([invokedynamicを使う条件](https://kotlinlang.org/docs/control-flow.html#bytecode-generation-on-the-jvm))。
`is EndEvent`を値との比較である`EndEvent`へ変えると、適用条件から外れます。 値を比較する版は`instanceof`と`areEqual`を使い、`typeSwitch`を生成しません。 JVM 21を対象にするだけで、任意の`when`がtype switchへ変わるわけではありません。
### type switchは、対象の値から分岐のindexを求める
`eligible`のbytecodeは、対象のobjectと整数0をstackへ積み、`invokedynamic`でtype switchを呼びます。 返り値は分岐を示す整数で、後ろの`tableswitch`が実際の処理へ進みます。 0が`DataEvent`、1が`EndEvent`に対応し、`DataEvent`の分岐ではcastして`count`を取得します。 型の選択は、JVMのbootstrapが設定するcall siteへ渡します。 sourceからJavaのreflection APIを1個ずつ呼ぶ実装ではありません([SwitchBootstraps.typeSwitch](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/runtime/SwitchBootstraps.html#typeSwitch%28java.lang.invoke.MethodHandles.Lookup,java.lang.String,java.lang.invoke.MethodType,java.lang.Object...%29))。
`tableswitch`へ置き換えた後も、各分岐のbodyはKotlinの元の処理です。 最適化は、型に対応する分岐を選ぶbytecodeの形を変更します。 sealedな型の宣言をJavaのenumへ変換したり、`DataEvent`のfieldを整数のtagへ置き換えたりする機能ではありません。 元のobjectを選択した型として扱うcastと、fieldを取得する呼び出しは残っています。
`SwitchBootstraps.typeSwitch`はJVM 21以降を対象にした入口です。 compilerを新しくしただけで、JVM target 17へ同じ呼び出しを出すわけではありません。 `-jvm-target 17`では、`eligible`にも`instanceof`を繰り返すbytecodeを生成します。 `-jvm-target 21`では`invokedynamic`と`tableswitch`です。 同じKotlin 2.4.20のcompilerでも、生成先のJVM targetが選択を変えています。
### guardと値の比較が、型だけの選択から外れる理由
`when`のguardは、型に一致した後で値の条件も検査します。 `is DataEvent if event.count > 0`は、同じ`DataEvent`でも`count`によって分岐を変えます。 JVM target 21でも、`guarded`は`instanceof`、`count`の取得、整数の比較を使い、type switchにはなりません。 型が分かっただけでは分岐先が1個に定まらないため、今回の適用条件からguardを除いています([whenのguardとbytecode生成](https://kotlinlang.org/docs/control-flow.html#bytecode-generation-on-the-jvm))。
`EndEvent`という値を直接caseに書く形では、Kotlinの等価比較の規則で判定します。 `is EndEvent`は型の検査です。 `EndEvent`を使う両方の条件で同じ分岐へ進めても、sourceで要求するoperationの種類は異なります。 値を直接比較する版のbytecodeには、`EndEvent.INSTANCE`を取得して`Intrinsics.areEqual`を呼ぶ処理が残ります。 compilerは、型を並べる構文と、任意の値の等価性を求める構文を、無条件に同じtype switchへまとめてはいません。
mutable collectionの型判定も、JVMの`instanceof`だけでは完了しない場合があります。 Kotlinがread-onlyとmutableを区別する型を、JVMの同じinterfaceへ対応付けるため、Kotlin側の型の意味を保つ追加の判定が関係します。 function typeでも、単純なclassの一致だけで必要な型検査を完了できるとは限りません。 条件がすべて`is`に見えても、直接type switchへ渡せる型に限るという制約が残ります。 2.4.20でstableになるのは適用条件を満たす生成方式であり、任意の型検査を同じ1命令へ変える規則ではありません([KotlinのJVM向け生成条件](https://kotlinlang.org/docs/control-flow.html#bytecode-generation-on-the-jvm))。
`when`にelse以外の条件が少なくとも2個必要で、すべて同じsubjectを検査する条件もあります。 subjectを持たず、各行へ異なるBoolean式を書く`when`は、type switchへ変換する対象ではありません。 別のobjectを調べる条件や、値の計算を伴う条件を混ぜた場合には、各式の意味を保存する必要があります。 bytecodeを短くする変換と、sourceに書いた判定の意味を変える変換を、同じ最適化として扱うことはできません。 JVM targetとcaseの型判定、値の比較、guardの有無が、type switchを生成する条件になります。
## coroutineの例外を、追加fieldを保ったまま複製する
coroutineのstack trace recoveryでは、例外が発生した場所と、別のcoroutineへ伝播した場所を結ぶstack情報を持たせます。 `kotlinx.coroutines`は元の例外を複製しますが、line番号やerror codeをconstructorに要求する例外は、messageとcauseだけから複製できません。 新しい`StackTraceRecoverable`を実装すると、例外自身が複製方法を定義できます([StackTraceRecoverableのAPI](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.coroutines.debug/-stack-trace-recoverable/))。
```kotlin
import kotlin.coroutines.ExperimentalStdlibCoroutineSupportApi
import kotlin.coroutines.debug.StackTraceRecoverable
@OptIn(ExperimentalStdlibCoroutineSupportApi::class)
class ParseFailure private constructor(
val offset: Int,
cause: Throwable?,
) : IllegalStateException("Invalid token at $offset", cause),
StackTraceRecoverable {
constructor(offset: Int) : this(offset, null)
override fun copyForStackTraceRecovery(): ParseFailure =
ParseFailure(offset, this)
}
```
`copyForStackTraceRecovery()`の直接呼び出しでは、offsetと元の例外への参照を引き継ぎます。
```kotlin
val original = ParseFailure(17)
val recovered = original.copyForStackTraceRecovery()
println(recovered.offset) // => 17
println(recovered.cause === original) // => true
```
元の例外をcauseにしながら固有のfieldを引き継ぐ実装なので、複製時に追加情報を失いません。
`copyForStackTraceRecovery()`は、複製しない場合に`null`を返すこともできます。 interfaceは全targetに存在しますが、`kotlinx.coroutines`がstack trace recoveryに使うのはJVMです。 APIはexperimentalで、宣言と利用の両方にopt-inが必要です([stack trace recoveryの適用範囲](https://kotlinlang.org/docs/whatsnew2420.html#support-for-coroutine-stack-trace-recovery))。
### messageとcauseだけでは復元できない例外を扱う
JVMの例外のstack traceは、通常、例外objectを作った実行経路に対応します。 例外を別のcoroutineが受け取り、後から再送出すると、受け取った側の論理的な呼び出し経路は、元のstackへ自動で追加されません。 stack trace recoveryは、例外を複製し、受け取った側の経路を含むstackへ置き換えるための規約です。 例外objectのidentityを保存したまま、元のstackだけを読む動作とは、保持する情報が異なります([StackTraceRecoverableが扱う伝播](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.coroutines.debug/-stack-trace-recoverable/))。
従来のheuristicな複製では、既知の形のconstructorを探せます。 messageとcauseを受け取る形、片方だけを受け取る形、引数なしの形などです。 しかし、`ParseFailure(offset: Int)`は、整数の位置を必要とします。 messageの文字列からoffsetをparseし直す方法は、例外固有の書式を知らないframeworkへ一般化できません。 追加したinterfaceのmethodでは、例外自身がoffsetを読み、同じclassの新しいinstanceを作ります。 frameworkが例外固有のfieldやconstructorの意味を推測する代わりに、型を定義した側が必要な状態を引き継ぎます。
`ParseFailure`のprivate constructorは、offsetとcauseを両方受け取れます。 通常の呼び出し元にはoffsetだけを受け取るconstructorを公開し、複製するmethodからは元の例外をcauseとして渡します。 したがって、例外を最初に作るAPIと、recovery用に複製するAPIで、渡す情報が異なります。 同じmessageを持つだけの別の例外へ置き換えた結果ではなく、業務上の位置情報と原因の連鎖を両方引き継いでいます。
### copyを拒否する結果と、copyに残す情報
`copyForStackTraceRecovery`の返り値はnullableです。 例外を複製したくない場合は`null`を返し、複製をしない方針を明示できます。 適切なconstructorが見つかる例外であっても、固有のidentityや内部状態を保存したい場合に、heuristicなcopyから外す入口になります。 `null`を返しても、例外を握り潰して処理を成功へ変える意味にはなりません。 recoveryのためのcopyを拒否する結果です([copyForStackTraceRecoveryの契約](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.coroutines.debug/-stack-trace-recoverable/copy-for-stack-trace-recovery.html))。
copyのstack traceは、後続のrecovery機構が`Throwable.setStackTrace`で上書きします。 copyを作るmethodの中で独自にstackを設定しても、後から上書きされます。 元の例外をcauseへ残せば、作成時の経路と、recovery後の経路を別の例外として追えます。 一方、元のsuppressed exceptionsを無条件にcopyすると、例外の循環を作る可能性があります。 APIの契約は、suppressed exceptionsをcopyしないことも推奨しています。
messageを変更したcopyも許可されますが、copyした例外がさらに別の伝播でrecoveryされる場合があります。 呼ぶたびに同じprefixを加える実装なら、伝播の回数だけmessageが伸びる結果になります。 元のmessageを別に保存するなど、再度copyされる入力も扱える設計が必要です。 `ParseFailure`はoffsetからmessageを組み立て直すため、元のmessageへprefixを重ねません。 fieldの保存と、messageの表示を、複製のたびに同じ規則へ対応付けています。
interfaceは標準libraryの宣言ですが、stack traceを再構成するのは非同期framework側です。 `copyForStackTraceRecovery()`が担うのは、固有のfieldとcauseを保存した例外の複製です。 coroutine間の伝播に対応するstackを組み立てる処理は、framework側に残ります。 JVM以外にもinterfaceの型は存在しますが、`kotlinx.coroutines`の当該recoveryはJVM向けです。 同じmethodを実装した事実だけから、すべてのtargetで同じstack情報が付くとは言えません([標準libraryのinterfaceとJVMのrecovery](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.coroutines.debug/-stack-trace-recoverable/))。
## 全要素の一致と重複を直接検査する
`allDistinct`はすべての要素が異なるか、`allEqual`はすべての要素が等しいかを返します。 選択したkeyで比べる`allDistinctBy`と`allEqualBy`もあります。 collection、sequence、arrayに対応します。 比較にはstructural equalityを使います([allDistinctのAPI](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/all-distinct.html)、[KEEP discussion](https://github.com/Kotlin/KEEP/discussions/495))。
```kotlin
@OptIn(ExperimentalStdlibApi::class)
fun main() {
println(listOf("A", "a").allDistinct())
println(listOf("A", "a").allDistinctBy { it.lowercase() })
println(emptyList().allEqual())
println(sequenceOf(3, 3, 3).allEqual())
}
```
出力は`true`、`false`、`true`、`true`です。 文字列を直接比べると`"A"`と`"a"`は異なりますが、`lowercase()`をkeyにすると同じ値になります。 空のcollectionへ適用した`allEqual`も、`true`を返します。 APIはexperimentalで、`ExperimentalStdlibApi`へのopt-inが必要です([collection APIの追加](https://kotlinlang.org/docs/whatsnew2420.html#new-functions-to-check-collection-elements-for-equality-and-uniqueness))。
### Booleanを返すために、反例を見つけた時点で列挙を止める
`allDistinct`は、全要素を新しいcollectionへ変換して返すAPIではありません。 1を2回返すsequenceでは、2個目で重複が確定します。 3個目を要求しないため、重複の後に置いた例外も発生しません。
```kotlin
@OptIn(ExperimentalStdlibApi::class)
fun distinctExample() {
var reads = 0
val duplicate = sequence {
reads++
yield(1)
reads++
yield(1)
error("read after duplicate")
}
println("${duplicate.allDistinct()}/$reads") // => false/2
}
```
`distinct().count() == count()`のように全体の列挙を必要とする構成とは、処理を止められる位置が異なります([allDistinctの早期終了](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.sequences/all-distinct.html))。
`allEqual`でも、最初と異なる要素を見つけた時点でfalseになります。 1、2、例外というsequenceも、2個目で相違が確定し、例外の位置へ到達しません。 反対に、すべて同じ値だったと結論するには、有限のsequenceを最後まで読む必要があります。 無限に同じ値が続く入力では、相違を発見せず、終端にも到達しないため、trueを返す時点がありません。 早期終了できる反例と、入力全体を読む成功条件を別に持つterminal operationです([allEqualのsequence API](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.sequences/all-equal.html))。
`allDistinct`の汎用的なIterable実装は、すでに現れた値を`HashSet`へ保存します。 先頭から読むたびにsetへ追加し、すでに存在する値ならfalseを返します。 空か1要素なら、setを作らずtrueにする経路もあります。 `allEqual`の実装は、先頭の値と後続の値を順に比較するので、過去の値すべてを保存する必要はありません。 同じBooleanを返す検査でも、重複を検出する条件と全一致の条件では、保持する状態が異なります([Kotlin 2.4.20のIterable実装](https://github.com/JetBrains/kotlin/blob/v2.4.20/libraries/stdlib/common/src/generated/%5FCollections.kt))。
`allDistinctBy`は、要素にselectorを適用し、返されたkeyを保存して比較します。 たとえば大小文字を区別しない名前なら、`lowercase()`で作ったkeyの重複を検出します。 ただし、入力の文字列を小文字へ置き換える処理ではありません。 元のcollectionを変更せず、検査する同一性の条件だけをselectorへ移します。 selectorが高価な計算や副作用を持つ場合には、早期終了した位置までの呼び出しだけが実行される操作になります([allDistinctBy](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/all-distinct-by.html))。
### NaNと符号付きzeroは、genericな等価性の規則で比較する
`listOf(Double.NaN, Double.NaN).allEqual()`はtrueです。 `listOf(-0.0, 0.0).allDistinct()`もtrueです。 浮動小数点の要素については、NaN同士を等しい値として扱い、負のzeroと正のzeroを異なる値として扱います。 比較は`Double.equals`と同じ規則です。 浮動小数点の式として直接比較するIEEE 754の結果とは異なります([allDistinctの浮動小数点の契約](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/all-distinct.html))。
`listOf(null, null).allDistinct()`はfalseです。 nullも要素として扱われ、2回現れれば重複になります。 一方、空のlistでは両方の検査がtrueになります。 1要素の場合にも、異なる2要素や相違する後続要素が存在しないためtrueになります。 空の入力を未判定の状態やerrorとして扱う契約ではなく、Booleanとしてtrueを返す条件が定められています。
`allEqualBy`では、最初の要素から得たkeyと、後続の要素のkeyを比較します。 たとえば同じ拡張子のpathだけかを検査する処理では、pathの全文が違っても、keyが等しければtrueになり得ます。 要素の等価性と、選択した属性の等価性を、異なるAPI名で表しています。 返り値がtrueでも、元のobject同士が同じinstanceであることや、すべてのfieldが同じであることまで意味しません。 selectorが捨てた情報は、Booleanの判定条件へ含まれません([allEqualBy](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/all-equal-by.html))。
`kotlin.test`のassertionには、失敗messageをlambdaで渡すoverloadが追加されています。 成功時にはmessageを組み立てず、失敗時にだけlambdaを実行します。 対象は`assertTrue`、`assertEquals`、`assertContentEquals`などです。 `ExperimentalKotlinTestApi`へのopt-inが必要です([lazy assertion messageのKEEP](https://github.com/Kotlin/KEEP/blob/main/proposals/stdlib/KEEP-0465-kotlin.test-lazy-assertion-messages.md))。
### assertionが成功した経路では、messageの計算を呼ばない
従来の文字列の引数では、assertionを呼ぶ前にmessageの式を評価します。 大きいcollectionを文字列へ変換したり、診断用のobjectを組み立てたりする処理も、成功するtestで実行されます。 lambdaを受け取るoverloadでは、失敗したときにだけmessageを作ります。 成功するassertionでは、messageのlambdaを呼びません。
```kotlin
@OptIn(ExperimentalKotlinTestApi::class)
fun lazyMessageExample() {
var messages = 0
assertTrue(true) { messages++; "unexpected failure" }
println(messages) // => 0
try {
assertEquals(1, 2) { messages++; "different values" }
} catch (failure: AssertionError) {
println(failure.message?.contains("different values")) // => true
}
println(messages) // => 1
}
```
messageを遅延しても、比較対象のexpectedとactualを計算する処理は必要です。 assertion全体を実行しない機能ではなく、失敗時にしか使わないmessageの計算を分離します。 lambda内の副作用も成功時には発生しないため、testに必要な状態の更新をmessageへ置く構成は、文字列を先に作る構成と同じ実行順序にはなりません。 `messages`の加算をlambdaへ置くと、失敗したassertionだけがcounterを増やします。 診断の構築を遅らせても、assertionの判定条件や実行時点は変わりません([KEEP 465のlazy messageの意味](https://github.com/Kotlin/KEEP/blob/main/proposals/stdlib/KEEP-0465-kotlin.test-lazy-assertion-messages.md))。
## Swift exportはsealed hierarchyをSwift enumへ対応付ける
Kotlin/NativeのSwift exportでは、sealed classとsealed interfaceの対応が加わりました。 exportされた値の`sealedType()`が、直接の派生型に対応するcaseを持つSwift enumを返します。 Swift側の`switch`でcaseを列挙し、処理の不足を検査できます。 Kotlinで派生型を追加すると、対応するcaseも変わります([Swift exportのsealed class](https://kotlinlang.org/docs/native-swift-export.html))。
### `sealedType()`で直接の派生型をSwiftのcaseへ渡す
Kotlinで`sealed interface`を宣言しても、以前のSwift側の型だけでは、派生型の集合を列挙できませんでした。 Swiftの`switch`へdefaultを置いた場合、新しい派生型をKotlin側へ追加しても、defaultへ入り処理の不足を検出できない場合があります。 新しいexportでは、直接の派生型に対応するcaseを持つenumを生成し、`sealedType()`から取得します。 Swiftのcompilerでenumのcaseを列挙できるため、defaultでまとめずに分岐を書けます([sealed hierarchyのSwiftへの対応](https://kotlinlang.org/docs/native-swift-export.html#sealed-classes-and-interfaces))。
公式の例では、`Shape`を直接実装する`Circle`と`Rectangle`が、Swift側の`.circle`と`.rectangle`へ対応します。 caseに関連付いた値から、対応するKotlinの型の値へ進めます。 型を判定するためにSwift側で文字列のclass名を比較する処理ではありません。 Kotlinの継承関係をexport時にenumのcaseへ反映し、Swiftが扱う分岐の集合として公開します。 `sealedType()`は、元のKotlin objectのすべてのfieldを別のSwift structへcopyするためのmethodではなく、型の選択を表す入口です。
多段のhierarchyでは、1回の`sealedType()`がすべての末端を平らに列挙するわけではありません。 直接の派生型へ分岐した後、派生型もsealedなら、さらに`sealedType()`を呼んで次の階層へ進みます。 したがって、階層の途中を追加する変更と、途中の型の下へcaseを追加する変更は、Swift側で不足が現れるswitchの位置が異なります。 基底型から直接派生する型の集合を渡すという契約が、入れ子の分岐にも保存されます。 defaultで未知の末端を一括処理する構成とは、型の追加を検出する範囲が異なります。
cross-language inheritanceでは、Kotlin側で宣言したbase classをSwift側で継承し、KotlinのinterfaceをSwiftで実装できます。 Swift objectをinterface型の引数としてKotlinへ戻すと、KotlinからSwift側の実装を呼び出せます。 Swiftのlibraryを使う処理をinterfaceの背後に実装するreverse importの形です([Swift exportとcross-language inheritance](https://kotlinlang.org/docs/whatsnew2420.html#cross-language-inheritance-in-swift-export))。
### Swiftの実装をKotlinのinterfaceへ戻す呼び出し経路
reverse importの構成では、Kotlin側が必要な操作をinterfaceとして宣言します。 Swift側は、exportされたKotlinのbase classを継承し、同時にinterfaceの操作を実装します。 Swiftで作ったobjectを、Kotlinのinterfaceを受け取るfunctionへ渡します。 Kotlin側からinterfaceのmemberを呼ぶと、Swiftで書いた実装へ処理が戻ります。 呼び出しの経路は、SwiftからKotlinへ入った後、同じ契約を通じてSwiftへ戻る形です。 SwiftだけからKotlinのfunctionを一方向に呼ぶexportとは、値が戻る向きと実装の所在が異なります([Swift exportのcross-language inheritance](https://kotlinlang.org/docs/native-swift-export.html#cross-language-inheritance))。
たとえばKotlin側が文字列のdigestを求めるinterfaceを持ち、Swift側がSwiftの暗号libraryを使って実装する構成にできます。 KotlinからSwiftのlibraryの宣言を直接importできなくても、Kotlinが定めた引数と返り値の契約を通じて処理を呼べます。 Kotlinが知るのはinterfaceで、platform固有のlibrary名や呼び出し方はSwift側にあります。 新しい継承対応が可能にするのは、境界を通るobjectがKotlinのinterfaceとして振る舞う入口です。 任意のSwift型のすべてのAPIを、Kotlinの型systemへ自動で公開する機能ではありません。
exportされるmodule名やpackageの対応も、Swiftの呼び出し元が名前を解決する条件になります。 Kotlinのmoduleは個別のSwift moduleへ対応し、設定ではroot moduleやexportするdependencyのmodule名を指定できます。 `flattenPackage`を指定すると、選んだpackageのprefixを生成するSwift側の名前から外せます。 interfaceを渡す経路が正しくても、Swift側がimportするmoduleと、生成された宣言の名前が合わなければ、sourceの名前解決は成立しません。 cross-language inheritanceと名前の生成は、同じexport工程の異なる出力です([Swift exportのmodule設定](https://kotlinlang.org/docs/native-swift-export.html#configure-kotlin-project))。
Swift exportは2.4.20でもAlphaで、Objective-Cのheaderを経由する従来のexportとは別の生成経路です。 現在の統合では、Xcode側から`embedSwiftExportForXcode`を使って生成したmoduleを取り込みます。 sealed hierarchyの新機能が加わった事実だけで、既存のObjective-C向けframeworkを再buildせずに同じSwift enumが現れるわけではありません。 Kotlin側の宣言、Swift exportの生成、Xcodeが参照する生成物という連なりで、Swiftから見えるAPIが決まります([Swift exportとXcodeの統合](https://kotlinlang.org/docs/native-swift-export.html#configure-xcode-project))。
Nativeの`klib`に対するincremental compilationはBetaになり、`kotlin.incremental.native=true`で有効にできます。 変更のない部分を再利用するbuild機能であり、アプリケーション実行時の処理を変える設定ではありません([Kotlin/Nativeのbuildに関する変更](https://kotlinlang.org/docs/whatsnew2420.html#kotlin-native))。
`klib`のincremental compilationは、Kotlin 1.9.20で導入された機能の安定性と性能を改善した段階です。 2.4.20ではBetaになりましたが、有効化するpropertyは引き続き明示します。 debug buildで変更のない入力を再利用することで、開発時のcompile量を減らす対象になります。 Swift exportの新しいcaseは、API生成の変更です。 incremental compilationの有効化によって、exportの公開範囲が広がるわけではありません。 生成するAPIの意味と、同じ意味の生成物へ到達するまでに再利用するbuild状態が、別に変更されています([klib incremental compilationのBeta化](https://kotlinlang.org/docs/whatsnew2420.html#improved-incremental-compilation-of-klib-artifacts))。
## Wasmはbuildの単位を選べる
Kotlin/Wasmの既定の`monolith`は、projectと依存先をまとめて1つのbinaryへcompileします。 全体の到達可能性を使って不要な宣言を取り除けますが、moduleを独立したbinaryとして更新する方式ではありません。 2.4.20には、moduleごとに再compileしbinaryを分割するmodeが追加されています([Wasmのcompilation mode](https://kotlinlang.org/docs/whatsnew2420.html#new-compilation-modes))。
`multimodule-open-world`はmoduleを独立してcompileし、独立したbinaryを生成します。 変更したmoduleだけを再compileできますが、moduleをまたぐ最適化は行いません。 `multimodule-closed-world`は全moduleを1回の起動で処理します。 変更したmoduleを再compileし、互いに依存するbinaryを出力します([multi-module modeの違い](https://kotlinlang.org/docs/whatsnew2420.html#new-compilation-modes))。
### moduleを個別にcompileできる条件と、全体を最適化できる条件
`monolith`は、projectとdependencyをまとめて扱い、最終的に1個のbinaryを生成します。 全体の到達可能性を判定できるので、使わない宣言を除くdead code eliminationを広く適用できます。 一方、UIの小さい変更でも、配布する単一binaryを作る工程にはproject全体の情報が関係します。 binaryを1個へまとめる範囲と、moduleを1個だけ再compileする範囲は異なります。 開発中の更新量を減らす要求が、複数moduleのmodeを追加した背景になります([Kotlin/Wasmのcompilation mode](https://kotlinlang.org/docs/whatsnew2420.html#new-compilation-modes))。
open-worldでは、moduleを独立にcompileします。 独立したbinaryを作るため、他のmoduleの実装へ踏み込む最適化は行いません。 たとえばcore、UI、applicationに分けた構成で、公開する契約を変えずにUIの実装だけを変更するなら、UIを独立した再compileの単位として扱う方式です。 単一binaryの全体最適化を維持したまま、任意の部分だけを入れ替える機能ではありません。 独立性を得る代わりに、moduleをまたいだ最適化の範囲を制限します。
closed-worldは、全moduleを1回のcompiler起動で扱います。 変更したmoduleを再compileしてから、相互に依存するbinaryを分けて出力します。 moduleごとのbinaryは、open-worldのような独立した生成物ではありません。 最適化は生成するbinaryの単位で適用するため、出力が複数になっても、すべての最適化がmonolithと同一になるという契約ではありません。 入力をまとめて把握する範囲、再compileする単位、生成するbinaryの関係が、modeごとに変わります。

開発用のmodule別buildと配布用の単一binary buildへ、同じsourceから分岐する図
`kotlin.wasm.compilationMode`には`multimodule-closed-world-only-in-dev`も指定できます。 開発buildはclosed-world、配布buildはmonolithになります。 開発時の再compileの単位と、配布するbinaryの構成を別に選ぶ設定です。 lambdaとfunctional interfaceの実装も変わりました。 個別のanonymous classを作らず、functionと共有base classを使います([Wasmのbinary生成](https://kotlinlang.org/docs/whatsnew2420.html#reduced-binary-size-for-lambdas-and-functional-interfaces))。
`multimodule-closed-world-only-in-dev`を指定した場合、開発用と配布用で同じbinary構成を使う前提にはなりません。 開発中はmoduleを分け、配布時にはmonolithへ戻します。 開発時の変更を反映する工程と、最終的に配るbinaryのsizeを抑える工程を、別の設定に対応付けられます。 ただし、開発時のbinary分割は、production buildで同じ形のcacheや生成物を再利用できる保証ではありません。 modeを選ぶpropertyは、compilerへの入力だけでなく、生成物の構成を変える設定です。
lambdaの実装の変更も、binaryへ含める構造を減らす対象です。 従来の方式では、lambdaやfunctional interfaceの実装ごとにanonymous classを生成していました。 新しい方式はfunctionを生成し、共有base classを使うため、個別のclassの構造を繰り返し含める量を減らします。 公式のKotlinConf applicationの比較では、Wasm binaryのsizeが約5〜10%減ったと報告されています。 同じ割合が任意のprogramへ適用されるという値ではなく、lambdaやfunctional interfaceを使う構成に対する実装変更の測定です([Wasmのlambda生成の変更](https://kotlinlang.org/docs/whatsnew2420.html#reduced-binary-size-for-lambdas-and-functional-interfaces))。
共有する実装へ移すことで、dynamicな呼び出しが増える面もあります。 したがって、binaryのsizeが小さくなった事実だけから、すべての処理が同じ割合で速くなるとは言えません。 sizeに影響するのは生成するclassやfunctionの構成で、実行時間には実際の呼び出し経路とruntimeの最適化も関係します。 compilation modeの選択によるbuild時間の差とも、測定する工程が違います。 2.4.20のWasmの変更では、開発時のcompile量と、配布するbinaryの構造に対する操作を個別に追加しています。
Wasmの相互運用では、`@JsFun`内のtop-levelの`require()`がerrorになりました。 従来compilerが生成していた`require`の公開をやめたためで、静的なmodule読み込みには`@JsModule`を使います。 companion objectの初期化順も、superclassをsubclassより先に初期化する順へ揃っています。 WASI targetにはWasmtimeを使う`wasmWasi { wasmtime() }`も追加されています([Kotlin/Wasmの相互運用と実行環境](https://kotlinlang.org/docs/whatsnew2420.html#kotlin-wasm))。
### compiler内部の`require`を、公開された読み込みAPIとして使わない
以前は、compilerが生成する`import-object.mjs`に`require`変数があり、`@JsFun`から参照できました。 しかし、生成したfileの内部変数を、利用側へ公開するAPIとして定めたものではありませんでした。 2.4.20は生成する宣言を取り除き、top-levelの`require()`を使う`@JsFun`へerrorを出します。 生成物の内部に存在した名前を使うsourceでは、読み込みの経路を変更する必要があります([top-levelのrequireの削除](https://kotlinlang.org/docs/whatsnew2420.html#changes-to-top-level-require-calls-in-jsfun-declarations))。
静的に読み込むmodule名が分かっている場合には、`@JsModule`で外部moduleの宣言へ対応付けます。 dynamicな読み込みではJavaScriptの`import()`を使う経路があります。 静的なdependencyを宣言することと、実行時の条件で読み込むことは、同じ文字列の呼び出しへ置換するだけの変更ではありません。 bundlerによるdynamic importの解析も関係するため、公式の例にはwebpackの解析から外す指定が含まれています。 Kotlinのannotation、JavaScriptの読み込み構文、bundlerが解析する入力の3段階へ影響する移行です。
companion objectの初期化順は、superclassをsubclassより先にする順序へ揃います。 中間のclassにcompanion objectがない場合にも、上位のclassの初期化を省略してよい条件にはなりません。 たとえば上位のcompanionが設定値を初期化し、下位のcompanionが設定値を読む構成では、読む側より先に上位の初期化を行います。 JVMとWasmで異なっていた順序を揃える変更です。 companion内の表示や登録など、副作用の順序も変わり得ます([companion objectの初期化順の変更](https://kotlinlang.org/docs/whatsnew2420.html#improved-companion-object-initialization-order))。
Wasmtimeの追加は、`wasmWasi`の実行先をKotlin Gradle pluginから選ぶ入口です。 browser向けの`wasmJs`をWASIのprogramへ変換する設定ではありません。 browserのDOMやJavaScriptのmoduleを使う構成と、WASIのhost機能を使う構成では、実行時に用意する依存が異なります。 生成するWasm binaryのtargetと、起動するruntimeを対応付けるために、`wasmWasi { wasmtime() }`を使います。 moduleの分割modeとは、選択する対象が異なるGradleの設定です([Wasmtimeの実行設定](https://kotlinlang.org/docs/whatsnew2420.html#support-for-wasmtime-in-the-kotlin-gradle-plugin))。
## Kotlin/JSとbuild toolの変更
Kotlin/JSでは、suspending lambdaをJavaScriptのasync functionとしてexportできます。 有効化するcompiler optionは`-Xsuspend-lambda-exporting`です。 data classのconstructorやpropertyに付けた`@JsExport.Ignore`の扱いも修正されました。 生成される`copy`や`componentN`が、非公開にした型に対する不要な警告を出さなくなります([Kotlin/JSのexport](https://kotlinlang.org/docs/whatsnew2420.html#kotlin-js))。
### suspending lambdaをJavaScriptのasync functionとして受け取る
Kotlin側のAPIが`suspend () -> String`をparameterに持つ場合、通常のfunctionを受け取るAPIとは継続の規約が異なります。 新しいexportでは、JavaScript側のasync functionを、Kotlinのsuspending lambdaへ対応付けます。 公式の`TaskRunner`の例では、TypeScript側が`async () => "done"`を渡し、`await runner.runTask(...)`で文字列を取得します。 callbackの中で非同期に処理する契約と、戻り値を待つ契約を、JavaScriptの`async`と`await`で表せる入口です([suspending lambdaのexport](https://kotlinlang.org/docs/whatsnew2420.html#support-for-exporting-suspending-lambdas-as-async-functions))。
有効化には`-Xsuspend-lambda-exporting`をcompiler optionへ追加し、公開する宣言へ`@JsExport`を付けます。 Kotlinのすべての内部lambdaを、無条件にJavaScriptへexportする設定ではありません。 exportするclassやmemberのsignatureが、JavaScript側の公開APIへ変換されます。 Kotlin内部でsuspendを使う事実と、境界を越えるsuspendingなfunction typeを公開することを区別します。 2.4.20が追加するのは、以前exportできなかったsuspending lambdaを含む宣言のbridgeです。
data classのexportでは、手で宣言したmember以外も公開する型の検査へ関わります。 たとえば接続objectを保持するpropertyを`@JsExport.Ignore`で外した場合です。 compilerが生成する`copy`や`componentN`は、同じ内部型をsignatureへ含める場合があります。 以前は、constructorやpropertyを除外した意図が生成memberへ十分に反映されず、exportできない型のwarningが残っていました。 2.4.20は、生成memberについてもIgnoreの条件を考慮します。 classをexportする判断と、classから自動生成するAPIの範囲を整合させる修正です([synthetic functionのexportabilityの修正](https://kotlinlang.org/docs/whatsnew2420.html#consistent-exportability-of-synthetic-functions-on-exported-data-classes))。
browser testには、Playwrightをbrowser driver、Mochaをtest runnerとして使うexperimental DSLが加わりました。 Gradleの`browser`内に`test`を宣言して使い、`ExperimentalJsTestDsl`へのopt-inが必要です。 Karmaを使う従来の仕組みから移るための機能として導入されています([Kotlin/JSのbrowser testing](https://kotlinlang.org/docs/js-running-tests.html))。
### browser driverとtest runnerを別の役割として設定する
新しいDSLのPlaywrightは、browserの起動と配布を管理します。 Mochaはtestを実行するrunnerで、2.4.20時点のbundlerはwebpackです。 browser testでは、KotlinをJavaScriptへcompileして実行用fileをまとめます。 browserを起動するdriverと、testの成功や失敗を記録するrunnerは別の役割です。 DSLが増えたことを、test frameworkの名前1個だけを置き換える変更として説明すると、管理する依存の範囲を取り違えます([新しいbrowser testing DSLの構成](https://kotlinlang.org/docs/whatsnew2420.html#a-new-dsl-for-browser-testing))。
`chromium()`、`firefox()`、`webkit()`で実行するengineを選びます。 新しいDSLでは、初回の実行時にPlaywrightが必要なbrowserをinstallし、配置を管理します。 systemへ普段使うbrowserが入っていることだけを前提にする構成とは、binaryの取得と選択の条件が異なります。 Karmaを使う`testTask`と、新しいDSLの`test`は別のblockであり、設定の名前も同じではありません。 `ExperimentalJsTestDsl`のopt-inは、新しい入口へ指定します([browserの設定とinstall](https://kotlinlang.org/docs/js-running-tests.html#configure-browsers))。
共通のtimeoutやheadlessの設定を持たせ、個別のrunnerで上書きする構成もできます。 たとえば全runnerへ共通の期限を設定し、Chromiumだけ別の期限へ変更する指定です。 Kotlin側のassertionのmessageを遅延する変更とは、testの異なる段階を扱います。 assertionの成否を決める処理はtestの中で動き、browser runnerのtimeoutは実行を待つ側の条件です。 新DSLが一括して管理する設定の中でも、対象のscopeが共通か個別かによって有効な値が変わります。
build tool側では、Gradle Problems APIの診断が問題のIDでgroup化されるようになりました。 Build tools APIにはJS、Wasm、metadataのtargetがexperimentalとして追加されています。 compilerの配布にはnative imageが追加され、実行用commandにはツール名の衝突を避ける`kotlinr`が導入されています([build toolとcompilerの変更](https://kotlinlang.org/docs/whatsnew2420.html#build-tools-api))。
### compilerのdiagnostic IDで、Gradleの問題をgroup化する
GradleのProblems APIは、build中の問題を構造化して報告する入口です。 2.4.20のKotlin Gradle pluginは、compilerのdiagnostic IDも情報へ含め、同じIDのdiagnosticをgroup化します。 単に表示messageの文字列が同じかを比較する方式より、compilerが分類した問題を追うための識別情報が残ります。 sourceの位置やmessageが違う複数の報告でも、同じdiagnosticの種類として関連付ける対象になります。 型検査の規則は変わらず、compilerからbuild systemへ伝える報告に識別情報を追加します([Problems APIへのdiagnostic IDの追加](https://kotlinlang.org/docs/whatsnew2420.html#improved-reporting-in-problems-api))。
Build tools APIは、build systemとKotlin compilerの間へ共通の入口を用意するAPIです。 2.4.20では、従来のJVMに加えてJS、Wasm、metadataのtargetへexperimentalな対応を追加します。 有効化するpropertyは、JSが`kotlin.js.runViaBuildToolsApi=true`、Wasmが`kotlin.wasm.runViaBuildToolsApi=true`です。 metadataには`kotlin.metadata.runViaBuildToolsApi=true`を使います。 同じcompilerを呼ぶための統合経路を選ぶ設定であり、targetをJSからWasmへ変更する設定ではありません([Build tools APIのtargetごとの有効化](https://kotlinlang.org/docs/whatsnew2420.html#support-for-kotlin-js-kotlin-wasm-and-kotlin-metadata))。
`kotlinr`は、Kotlin runnerのcommand名です。 `kotlinc`というcompilerの名前を、同じ意味で改名する変更ではありません。 従来の`kotlin`はKotlin Toolchainのcommandと名前が衝突するため、runnerを`kotlinr`へ分け、旧名で実行した場合には新しい名前を案内するwarningを出します。 sourceをcompileする入口と、compileしたKotlin programを起動する入口を、呼び出すtoolの役割に合わせて区別する変更です([runner commandの変更](https://kotlinlang.org/docs/whatsnew2420.html#changed-the-kotlin-runner-command-from-kotlin-to-kotlinr))。
native imageは、compiler自身をnative executableとして配布するexperimentalな方式です。 通常の`kotlinc`の代替として起動時間を短くする対象ですが、Kotlin/JVMのアプリケーションをKotlin/Nativeへ変更する設定ではありません。 compilerを実行する形式と、compilerが生成するtargetは別です。 配布するnative imageにはcompiler pluginも同梱されます。 Serialization、Compose、All-open、No-argのほか、SAM with receiver、Assignment、Lombok、Power-assertです。 pluginを指定する入口には`-Xplugin`または`-Xcompiler-plugin`を使います([compiler native imageの配布範囲](https://kotlinlang.org/docs/whatsnew2420.html#native-image))。
### TypeScript 7.0のnative compilerと7.1 Betaの新API
URL: https://www.sakashita.page/typescript-7-0-and-7-1-beta-release-notes-and-analysis/
Last updated: 2026-10-25T08:59:59.000Z
TypeScript 7.0は2026年7月8日に正式公開されました。 7.0ではcompilerがGoによるnative実装へ移り、parsing、type checking、emitを並列に処理します。 10月20日には、importの新機能と新しいcompiler APIを加えた7.1 Betaも公開されています([7.0の公式発表](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/)、[7.1 Betaの公式発表](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/))。
## `tsc`の実装と、生成するJavaScriptを分けて考える
native化されたのは開発時に動くcompilerです。 TypeScriptのプログラムをGoの実行ファイルへ変換する機能ではなく、従来と同じくJavaScriptを出力します。 discriminated unionを使う例では、型検査後もcaseごとの分岐がJavaScriptへ残ります。
```typescript
type Result = { kind: "ready"; value: number } | { kind: "missing" };
function describe(result: Result): string {
switch (result.kind) {
case "ready":
return `value=${result.value}`;
case "missing":
return "missing";
}
}
console.log(describe({ kind: "ready", value: 42 })); // => value=42
```
TypeScript 7.0.2とNode.js 26.12.0でのcompileと実行は、次のcommandに対応します。
```sh
tsc --strict --target es2024 --module nodenext --outDir dist main.ts
node dist/main.js
```
`Result`の型情報は出力から消え、`switch`と文字列を返す処理がJavaScriptへ残ります。
並列化のうち、type checkingにはworkerごとの型情報が必要です。 7.0はworkerを既定で4個作り、各workerに担当するファイルを決まった方法で割り当てます。 共有する依存型の計算は重複する場合があるため、workerを増やすとmemory使用量も増え得ます([7.0のtype checkingの並列化](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#type-checker-parallelization))。

parsingからcheckerとemitへ分かれ、診断結果とJavaScriptを出力する図
`--build`でproject referenceを処理する場合は、`--builders`が同時にbuildするproject数を制御します。 たとえば`--builders 4 --checkers 4`なら、最大16個のcheckerが動き得ます。 `--singleThreaded`はcheckerを1個にするだけでなく、parsingとemitも単一threadにします。 7.0.2の`tsc --help --all`は、3個のoptionと既定値を表示します。
worker数の変更で、型情報の評価順に依存する結果も変わる場合があります。 同じ入力に対する再現性を揃える場合、`--checkers`の値まで同じにします。 `--builders`による並列実行もprojectの依存関係に従います。 個数を増やしても、依存先の完了前に後続のprojectを実行できません([並列buildの条件](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#project-reference-builder-parallelization))。
### fileごとに分けられる処理と、共有する型へ依存する処理
parsingは、fileの文字列を構文木へ変換する処理です。 あるfileの構文を読むために、別のfileの型検査をすべて終える必要はありません。 emitも、fileごとに処理を分けられる部分があります。 対してtype checkingでは、importした型、global scope、genericな型の計算結果など、fileを越える情報を使います。 source fileをCPU数と同じ個数に分けるだけでは、共通のdependencyを各threadで繰り返し調べる処理が増えるため、段階ごとに異なる並列化が必要です([7.0の処理段階と並列化](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#custom-scaling-parallelization-and-controls))。
checker workerは、型を扱う独自の状態を持ちます。 共通の型計算が重複する一方、同じfile集合を同じworker数で処理する場合には、担当の分割を固定します。 型を必要とした順序によって結果が変わる経路を持つため、単に空いたworkerへ次々とfileを渡す構成とは異なります。 処理を並べる順序の自由度と、既存の型検査の再現性を同時に扱うための分割です。 `--checkers`は、workerの個数だけでなく、各workerが持つ型情報の分割にも影響します。
workerを8個へ増やすと、4個の場合より広く並列に処理できる場合があります。 ただし、共有するdependencyを調べる量や、型情報を保存するmemoryも増え得ます。 fileが少ないprojectや、同じ巨大な型へ依存するfileが多いprojectでは、worker数と処理時間が比例する条件にはなりません。 CPUのcore数だけでなく、型のdependencyの重なりが並列化の効率を決めます。
### project referenceの並列化は、dependency graphの幅に制限される
`--builders`が扱うのは、`--build`で選択する複数projectの関係です。 たとえば基礎libraryのAをBとCが参照し、アプリケーションDがBとCを参照する構成では、BとCを同時に進められる段階があります。 一方、AからB、BからC、CからDという1本の連鎖なら、project数が4個あっても、同じ幅の並列性はありません。 builderの個数を増やしても、依存する宣言の準備が完了していないprojectを、依存関係を無視して先へ進める設定ではありません([project reference builderの適用範囲](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#project-reference-builder-parallelization))。
project内のchecker数と、同時に動くproject数は掛け合わさります。 `--builders 4 --checkers 4`は、compiler全体でcheckerを4個に限る指定ではありません。 最大4個のprojectで4個ずつcheckerを使えるため、最大16個になります。 CIの外側でもjobを複数並べる場合には、同時に起動したcompilerごとに同じ構成ができます。 個々のprojectで適度な設定でも、複数projectや外側のjobへ重ねると、総memoryの条件が変わります。
`--checkers 1`は、type checkingのworkerを1個にする指定です。 `--singleThreaded`は、parsingとemitも含めて単一threadへ制限します。 したがって、type checkingを1個へ制限しても、compiler全体を単一threadへ制限したことにはなりません。 外側のtask runnerで並列性を管理する場合や、単一thread同士を比較する場合には、制限する段階が異なります。 `--builders`はproject間、`--checkers`はproject内の型検査を制御します。 `--singleThreaded`はcompiler全体のthread利用を制限します([single-threaded mode](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#single-threaded-mode))。
### native化しても、出力先のJavaScript runtimeは別に選ぶ
`Result`はTypeScriptの型で、実行時のtagを自動で検査するobjectではありません。 出力されたJavaScriptには、`result.kind`を読む`switch`が残り、型aliasの宣言は残りません。 native化によって型情報がGoのruntimeへ移り、アプリケーションの実行時にも検査される構成ではありません。 外部から受け取る値を`Result`として使う前のvalidationも、compile時の型検査だけでは代行されません。 compilerの実装言語、sourceの型表現、生成したプログラムが動くruntimeは、別々の対象です。
`--target`は出力するJavaScriptの構文と、既定で読むlibrary宣言の選択に関わります。 compiler自身がnative executableになっても、古いJavaScript runtimeに存在しないAPIを自動で実装する機能は加わりません。 同様に、`--module`が決めるmoduleの表現と、実際のfileを読み込むNode.jsやbundlerの解決処理も異なります。 compilerの更新だけで、生成先のruntimeやpackageのmodule構成まで一括して更新されるわけではありません([targetの設定](https://www.typescriptlang.org/tsconfig/target.html)、[moduleの設定](https://www.typescriptlang.org/tsconfig/module.html))。
## 既定設定と、従来のcompiler APIへの依存
7.0は6.0で導入された既定設定を引き継ぎ、6.0でdeprecatedだった設定や構文をerrorにします。 `strict`と`noUncheckedSideEffectImports`は既定で有効です。 `module`は`esnext`、`rootDir`は`./`が既定になり、`stableTypeOrdering`は無効にできません([TypeScript 6.0の移行に関する変更](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/))。
`--watch`も実装が変わり、Parcelのfile watcherをGoへ移した実装を使います。 依存ファイルを繰り返しpollingする方式から、OSのfile notificationを利用する方式へ移すための変更です([7.0のwatch mode](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#improved---watch-mode))。
compilerをlibraryとして呼ぶツールでは、CLIの置き換えとAPIの移行が別になります。 従来の`typescript` packageのcompiler APIが必要な用途には、6系を提供する`@typescript/typescript6`が用意されています。 7.1 Betaで導入されるAPIは、従来のAPIのimport先だけを変える互換APIではありません([7.0のAPIと互換package](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/))。
### `rootDir`の既定値は、出力先のdirectory構成へ現れる
`rootDir`は、出力するfileの相対pathを計算する基準です。 `tsconfig.json`の隣に`src`があり、sourceが`src/index.ts`にあるprojectでは、基準をproject rootにするか`src`にするかで、出力のpathが変わります。 `outDir`が`dist`なら、project rootを基準にすると`dist/src/index.js`、`src`を基準にすると`dist/index.js`という差になります。 sourceのimport文を1行も変えなくても、生成物を参照するpackageのentry pointや実行commandに影響する変更です([rootDirと出力の関係](https://www.typescriptlang.org/tsconfig/rootDir.html))。
`rootDir`は、compile対象のfileを選別するための設定ではありません。 入力を選ぶ`files`、`include`、`exclude`と、出力時の相対pathを決める基準は役割が違います。 従来の出力構成を保存したいprojectでは、実際に基準にしていたdirectoryを`rootDir`として明示する変更になります。 出力先で`src`が1段増えた場合に、compilerがsourceを余計に読み込んだ結果だと決め付けると、変更すべき設定を取り違えます。 7.0で変わる既定値は、明示済みの設定を上書きするものではありません。
7.0.2では、`src/index.ts`と`outDir: "dist"`だけを持つprojectにTS5011を出します。 共通のsource directoryが`./src`であるため、`rootDir`を明示するように求める診断です。 `noEmitOnError`を有効にしていない場合は、exit code 2でも`dist/src/index.js`を生成します。 `--rootDir layout/src --outDir layout/dist-explicit`を明示すると、生成先は`dist-explicit/index.js`になります。 出力fileが存在する事実だけでは、compilerが成功したことまで判定できない例にもなっています。
errorがあるbuildから新しいJavaScriptを出したくない場合は、`noEmitOnError`を有効にします。 既定値は`false`で、errorがあってもemitする動作を許します。 `true`では、JavaScriptだけでなくsource mapやdeclaration fileの出力も抑止します([noEmitOnErrorの対象と既定値](https://www.typescriptlang.org/tsconfig/noEmitOnError.html))。 出力pathを決める`rootDir`と、emitの可否を決める`noEmitOnError`は、異なる工程へ作用します。 `rootDir`を直せばTS5011の原因を除けますが、別の型errorが残る場合のemit条件まで変更しません。 逆に、`noEmitOnError`だけを有効にしても、TS5011が要求する基準directoryは決まりません。 pathの設定を移行する処理と、不正なbuildの生成物を出さない設定を、必要な動作へ合わせて指定します。
`types`の既定値も空の配列へ変わっています。 可視範囲の`@types` packageを自動的にglobalへ取り込む前提のprojectでは、Node.jsの名前やtest用のglobalを解決できなくなる場合があります。 `types: ["node"]`は、Node.js向けの型をglobalへ取り込む指定です。 `types: ["*"]`は従来の探索へ戻す経路ですが、個別のpackageを列挙する指定とは、取り込む集合が異なります。 空の既定値は、明示したimportのmoduleを型検査できなくする設定ではなく、globalへ自動で含める型の集合に関わります([typesが制御する範囲](https://www.typescriptlang.org/tsconfig/types.html))。
`strict=true`の既定値は、以前に省略していたstrict系の検査を有効にします。 すでに`strict`をtrueにしていたprojectでは、既定値だけを理由に検査がもう一段強くなるわけではありません。 一方、省略によってfalseを得ていたprojectは、引数の暗黙の`any`やnullの扱いなど、検査する条件が変わり得ます。 `strict`をfalseにする明示的な設定は残るため、更新時に増えた診断がsourceの変更によるものか、既定値によるものかを分けられます([strictがまとめる検査](https://www.typescriptlang.org/tsconfig/strict.html))。
`noUncheckedSideEffectImports`は、値を束縛しない`import "./setup"`のようなimportも、解決できるか検査する設定です。 値を使用していないことと、moduleを実行するための依存が存在しなくてよいことは異なります。 CSSなどをbundlerが扱うprojectでは、対応するambient declarationも解決の入力になります。 ただし、ambient declarationへ一致しても、実fileの存在まで検証した結果にはなりません。 7.1 Betaのattributes付き宣言にも同じ条件が残ります([noUncheckedSideEffectImports](https://www.typescriptlang.org/tsconfig/noUncheckedSideEffectImports.html))。
### 削除されたoptionは、6.0のdeprecation抑制では復活しない
7.0.2は`--target es5 --noEmit main.ts`にTS5108を出し、`target=ES5`が削除されたと報告します。 `--baseUrl .`には、削除されたoptionを示すTS5102を出します。 両方とも型検査の途中でsourceの型が合わなくなった結果ではなく、compilerのoptionとして受け付けない入力です。 6.0で使えた`ignoreDeprecations: "6.0"`は、7.0で削除した機能を再実装する指定ではありません。 deprecationを一時的に抑える段階と、optionが存在しない段階は、buildの失敗する位置が異なります([6.0から7.0へ持ち越されない設定](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/#breaking-changes-and-deprecations-in-typescript-60))。
ES5向けの`target`と`downlevelIteration`は、古い構文や反復の変換に関わっていました。 moduleではAMD、UMD、SystemJS向けの値が削除対象です。 module resolutionでは`classic`と`node10`が対象になります。 同じNode.jsという名前でも、`node10`の探索規則は現在のpackage exportsなどを扱う`nodenext`と同一ではありません。 生成するmodule形式と、dependencyを解決する規則の両方が、従来の設定の移行対象です([module resolutionの設定](https://www.typescriptlang.org/tsconfig/moduleResolution.html))。
`baseUrl`の削除は、path aliasの文字列を、出力されたJavaScriptへ自動で変換する機能を追加しません。 `paths`はcompilerがmoduleを探す際の対応付けであり、runtimeのimport specifierを書き換える設定ではありません。 aliasを維持する場合には、compilerが探す対象と、runtimeやbundlerが解決する名前の対応が残ります。 `baseUrl`の削除は、すべてのbare specifierの意味を変更する操作ではありません([pathsとemitの関係](https://www.typescriptlang.org/tsconfig/paths.html))。
`outFile`も削除対象で、compiler自身が対応したmodule形式を1個のfileへまとめる経路は使えなくなります。 さらに、`esModuleInterop=false`、`allowSyntheticDefaultImports=false`、`alwaysStrict=false`という古い設定も対象です。 source上では、namespaceを表す旧式の`module`構文、import assertionの`assert`構文、`no-default-lib` directiveが対象になります。 7.0は6.0の型検査へ近付けたnative実装です。 ただし、6.0でdeprecationを無視できた機能も、削除の対象になります([6.0でdeprecatedとなった機能](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/#deprecated-outfile))。
型の表示順序に関わる`stableTypeOrdering`も、7.0では固定された動作になります。 従来のcompilerには、型を最初に見つけた順序で内部IDを割り当て、IDでunionの構成要素を並べる処理がありました。 直接関係しない定数をfileの前へ置いただけでも、declaration fileへ出すunionの順序を変えてしまう実装でした。 6.0の`stableTypeOrdering`は、7.0へ移行したときの順序へ合わせるための設定です。 型の集合として同じunionでも、生成した宣言を文字列として比較する処理には差が現れる対象になります([6.0のstable type ordering](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/#the---stabletypeordering-flag))。
### file watcherの実装を、型検査の並列性と独立させる
watch modeでpollingを使う場合は、変更の有無を確かめるため、一定の間隔で多数のfileを調べます。 `node_modules`を含む大きいprojectでは、変更のないfileを繰り返し調べる処理も増えます。 7.0のwatcherは、ParcelのC++実装をGoへ移した独立のpackageを基礎にし、OSの変更通知を利用します。 compilerの型検査を速くする処理とは別に、変更を検出するための継続的な負荷を扱う変更です([7.0のwatcherの実装](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#improved---watch-mode))。
Goの標準libraryには、必要なcross-platformのfile watching APIがありませんでした。 既存のC++実装を直接組み込む方式なら、Go以外のbuild toolchainが必要になります。 TypeScript側はGoへのportと小さいassemblyの補助を使い、C++のtoolchainを追加せずにwatcherをbuildする構成を選びました。 watchする対象を決めるcompiler側の判断と、変更通知を受けるplatform側の実装を分けています。 `--checkers`を増やす設定は、watch対象のfileをOSが通知する仕組みを置き換える指定ではありません。
7.1 Betaでは、native化した後の実装にも追加の最適化が入っています。 unionの構築、型情報のcache、ASTの重複fieldの整理、不要なallocationの削減など、処理する情報と保存する情報の両方が対象です。 公式の`--noEmit`、checker 4個の比較では、`mui-docs`の時間が22.2秒から11.9秒、memoryが8093 MiBから4079 MiBになっています。 一方、`bluesky`の時間は4.48秒から3.89秒で、同じ比率ではありません。 入力に含まれる型計算や構文の分布によって、内部実装の削減が総量へ現れる割合も異なる結果です([7.1 Betaの公式測定](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/#optimizations))。
測定の比較対象は、7.0と7.1 Betaのcompilerです。 `--noEmit`なので、JavaScriptの出力やbundlerの処理を含むbuild全体の所要時間でもありません。 compilerの型検査が占める範囲へ対する改善と、別の工程を含むCI全体の時間を同じ割合で減らす結果は区別されます。 native compilerの内部改善と並列化の設定は、開発時の処理へ作用します。 アプリケーションのruntimeは、生成したJavaScriptを実行する段階の性能へ作用します。
## 7.1 Betaではimport attributesからmoduleの型を決める
従来の`declare module "*.txt"`は、ファイル名の拡張子だけでmoduleの型を宣言していました。 しかし、同じファイルをtextとして読むかbyte列として読むかは、runtimeやbundlerに渡すimport attributesでも変わります。 7.1 Betaではambient module declarationにも`with`を書き、attributesに対応した型を宣言できます([実装PR #63931](https://github.com/microsoft/TypeScript/pull/63931))。
```typescript
declare module "*" with { type: "text" } {
const content: string;
export default content;
}
declare module "*" with { type: "bytes" } {
const content: Uint8Array;
export default content;
}
```
宣言を`assets.d.ts`に置くと、同じ名前のファイルから異なる型をimportできます。
```typescript
import text from "./missing.asset" with { type: "text" };
import bytes from "./missing.asset" with { type: "bytes" };
const title: string = text;
const data: Uint8Array = bytes;
```
型検査の条件は7.1.0-betaの`--strict --noEmit --module esnext --moduleResolution bundler`です。 attributesなしのimportは、一致する宣言がなくTS2307になります。
```typescript
import missing from "./missing.asset";
// TS2307: Cannot find module './missing.asset' or its corresponding type declarations.
```
attributesを条件にする宣言へ解決できなかったためです。
ambient moduleに一致したimportでは、ファイルの存在を検証せず、宣言した型を与えます。 型検査を通った後の読み込みや、textとbytesの変換はruntimeやbundlerが行います([ambient moduleへの解決と存在確認の範囲](https://github.com/microsoft/TypeScript/pull/63931))。
### 拡張子の分類と、読み込む表現の選択を分ける
bundlerのCSS Modulesと、ブラウザーの`CSSStyleSheet`では、同じ`.css`から返す値の形が異なります。 file名のpatternだけで常に`CSSStyleSheet`だと宣言すると、attributesを付けなかった別の読み込みも同じ型になります。 7.1 Betaのattributes付きambient declarationは、file名だけでは足りなかった条件を、moduleの型を選ぶ入力へ含めます。 `type: "text"`と`type: "bytes"`を分けて宣言すると、同じspecifierへ別の型を付けられます([PR #63931](https://github.com/microsoft/TypeScript/pull/63931))。
`declare module "*" with { type: "text" }`の`*`は、特定の拡張子へ限定しないpatternです。 `.txt`と`.md`の両方を文字列として読む環境なら、拡張子ごとの宣言を同じ内容で複製せずに済みます。 一方、`type: "bytes"`の宣言へ一致したimportは、文字列のmethodを持つ値にはなりません。 保持するのがdecode済みの文字列か、decode前のbyte列かを型として区別します。 TypeScriptが同じfileを2回読み、内容から適切な型を推論した結果ではありません。
`missing.asset`が存在しなくても、textとbytesのimportはambient declarationへ一致します。 attributesを外したimportは、宣言と実fileのいずれへも解決できず、TS2307を出します。 解決できた事実が保証するのは、型を与える宣言との対応です。 packaging時にfileを含め忘れた失敗や、runtimeが未知のattributeを拒否する失敗を、ambient declarationだけで検出するわけではありません。
attributesに対応する型宣言は、7.1の組み込みlibraryへ一律に含まれるものではありません。 利用するruntimeやbundlerが返す型に合わせて、project側の宣言として定義します。 たとえばDOMの型を含めないNode.js向けprojectへ、`CSSStyleSheet`の宣言を無条件に追加した場合です。 globalのDOM型も必要になります。 attributes名が同じでも、読み込み方式の異なる環境では、宣言する型を合わせる必要があります([7.1 Betaのambient declarationの提供範囲](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/#support-for-import-attributes-in-ambient-modules))。
## source phase importはmoduleを実行する前の段階を取り出す
7.1 Betaは`import source`と`import.source()`にも対応します。 通常のimportがmoduleを読み込み、依存関係を結び、実行した結果を扱うのに対し、source phaseでは解析後かつ依存先の読み込み前のmodule sourceを受け取ります。 WebAssemblyなら`WebAssembly.Module`を得て、必要なimportsを渡しながらinstanceを作る使い方ができます([実装PR #63915](https://github.com/microsoft/TypeScript/pull/63915))。
対応する`module`は`esnext`、`nodenext`、`preserve`で、型定義には`esnext`が必要です。 TypeScriptで構文を受け付けることと、実行環境がmodule sourceを扱うことは別の条件です。 JavaScriptのmodule sourceを後から実行する機能まで、7.1 Betaのcompilerが代行するわけではありません([source phase importの適用範囲](https://github.com/microsoft/TypeScript/pull/63915))。
### module sourceと、instanceのexportを区別する
通常のimportで使うmodule namespaceには、moduleを評価した後のexportが現れます。 source phase importが受け取るのは、解析されたmodule sourceを表すobjectです。 依存moduleを読み込み、linkし、module内のcodeを実行する段階へ進む前に取得します。 同じspecifierでも、返るobjectの表す処理段階は異なります。 sourceを取得した時点では、moduleのtop-levelは未実行です([source phase importを実装したPR #63915](https://github.com/microsoft/TypeScript/pull/63915))。
WebAssemblyでは、sourceのobjectが`WebAssembly.Module`です。 moduleをinstance化する際に、必要なimport objectを渡せます。 同じmodule sourceから、異なるimportやmemoryの設定を渡して複数のinstanceを作る構成が可能です。 module sourceをcacheする対象と、実行時の状態を持つinstanceをcacheする対象を分けられます。 source objectは、通常のmodule namespaceとは異なります。 exportされたfunctionの取得には、instance化の処理が必要です([Node.jsのWasm source phase imports](https://nodejs.org/api/esm.html#wasm-source-phase-imports))。
staticなsource phase importで許可するのはdefault importです。 `import source { operation } from ...`のように、moduleのexportから個別の名前を取り出す構文ではありません。 dynamicな形には`import.source(specifier)`を使います。 通常の`import()`が返すmodule namespaceと同じ型として扱うのではなく、module sourceを返す操作として型付けします。 TypeScriptのglobalな型では、`AbstractModuleSource`が対応する入口になります。
source objectはidentityを保存します。 同じmodule sourceを複数回importした値同士は、`Map`や`Set`のkeyとして比較する対象になります。 一方、同じsourceからinstance化した値は、個別の実行時の状態を持つ別のinstanceです。 sourceのidentityの一致は、instanceのidentityの一致を意味しません。 解析済みの入力を再利用する関係と、実行時のinstanceを共有する関係が、別の操作として表れます([ECMAScriptのsource phase imports proposal](https://github.com/tc39/proposal-source-phase-imports))。
JavaScript moduleのsourceを任意の環境で再実行する仕組みまで、今回のTypeScriptが完成させるわけではありません。 compilerは構文をparseし、型を与え、選択したmodule設定に応じて出力を扱います。 module sourceを取得し、後続の処理へ渡す機能は、runtime側の対応にも依存します。 WebAssemblyのinstance化の用途と、将来のJavaScript moduleの制御に関するproposalを、現在すべて同じ操作が使える機能としてまとめることはできません。 7.1 Betaの追加範囲は、source phaseをsourceに記述できる言語処理と型の入口です。
## 新APIではGo側のresourceの生存期間を閉じる
7.1 Betaのcompiler APIは、同期clientの`typescript/sync`と非同期clientの`typescript/async`に分かれます。 ASTの型やfactory、file systemを表すAPIも別のentry pointです。 同期APIは、作成したprogramに属するsemantic diagnosticを返します([7.1 Betaのcompiler API](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/#typescript-71-api))。
```typescript
import { API } from "typescript/sync";
using api = new API();
const options = api.parseCommandLine([
"--strict", "--noEmit", "--target", "es2024", "check-input.ts",
]);
using program = api.createProgram(options.fileNames, options.options);
const diagnostics = program.getSemanticDiagnostics();
console.log(diagnostics.map((diagnostic) => diagnostic.code)); // => [ 2322 ]
```
`check-input.ts`には、文字列を整数の型へ代入する宣言を置きます。
```typescript
export const answer: number = "forty-two";
```
API clientのcompile条件は7.1.0-betaと`--lib esnext,dom`、runtimeはNode.js 26.12.0です。 TS2322は、文字列から`number`への代入の不一致を表します。
`API`や`Program`は、JavaScript側のobjectに対応するresourceをGo側にも保持します。 JavaScriptのGCだけではGo側のresourceを解放できないため、`using`でscopeの終了時に破棄します。 snapshotを更新するAPIでも、更新前と更新後のprogramは別のsnapshotに属するobjectとして扱います([7.1 Betaのresource管理とsnapshot](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/#snapshots))。
### CLIと同じ設定の読み込みを、自動では行わないAPIの呼び出し
`parseCommandLine`へ渡したfile名とoptionから、`Program`を作ります。 `tsc -p project`と同じように、周囲の`tsconfig.json`を自動探索する実装にはしていません。 したがって、同じdirectoryでCLIとAPIを呼んだ事実だけでは、同じ入力集合とcompiler optionを使う保証にはなりません。 CLIが読み込むproject設定と、APIへ渡すoptionを明示する処理を対応付けて初めて、同じ条件の検査になります。 configの探索を省略したAPIの例では、入力の違いによって型検査の意味も変わります([7.1 Betaのbatch compilationの例](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/#typescript-71-api))。
diagnosticも1種類ではありません。 global、program、syntactic、semanticのdiagnosticは、異なる処理段階の問題を表します。 `getSemanticDiagnostics()`は型の代入などのsemanticな問題を返し、globalやsyntacticなdiagnosticをまとめて返す入口ではありません。 独自のbuild toolを作る場合、検査する段階を省略した結果と、diagnosticが0件だった結果を混同しない構成が必要です。 CLIの出力を再現する要求と、特定の型情報を照会する要求では、APIで組み立てる処理の範囲が異なります。
同期clientと非同期clientは、同じ名前を別のlibraryへ置いたものではありません。 非同期clientはPromiseに基づく呼び出しを提供し、同期clientは呼び出しの結果を同期的に取得します。 ASTの型やenumは`typescript/ast`、type guardは`typescript/ast/is`、構築と更新のfactoryは`typescript/ast/factory`に分かれます。 compilerを制御する操作と、構文木の型を参照する用途を、importする入口から分けています。 従来の巨大なroot exportを前提としたtoolのsourceは、必要な入口の選択とAPIの使い方を変更する対象です。
### snapshotの更新後も、古いdiagnosticを読む
virtual fileの初期状態を`export const answer: number = "forty-two";`とすると、整数への文字列の代入にdiagnosticを出します。 `createFileSystemWithLib`でsource fileを定義し、compilerの標準libraryも読めるfile systemを使っています。 同じfileを`export const answer: number = 42;`へ更新するlayerは、新しいsnapshotにだけ適用されます。
```typescript
import { ScriptTarget } from "typescript/ast";
import { createFileSystemLayer, createFileSystemWithLib } from "typescript/fs";
import { API } from "typescript/sync";
const fileName = "/virtual/report.ts";
using api = new API();
using before = api.createSnapshot({
fileSystem: createFileSystemWithLib([
[fileName, 'export const answer: number = "forty-two";'],
]),
createPrograms: [{
rootFiles: [fileName],
compilerOptions: { strict: true, noEmit: true, target: ScriptTarget.ES2024 },
}],
});
using beforeProgram = before.operation.createdPrograms[0];
console.log("before", beforeProgram.getSemanticDiagnostics().length); // => before 1
using after = before.update({
fileSystem: createFileSystemLayer([
[fileName, "export const answer: number = 42;"],
]),
ensurePrograms: [beforeProgram.id],
});
using afterProgram = after.getProgram(beforeProgram.id);
if (!afterProgram) throw new Error("Updated program missing");
console.log("after", afterProgram.getSemanticDiagnostics().length); // => after 0
console.log("before-again", beforeProgram.getSemanticDiagnostics().length); // => before-again 1
```
更新は、既存のprogramの内容を同じobjectの中で無条件に書き換える操作ではありません。 新しいsnapshotを作り、更新後のprogramを取得します。 programのIDはsnapshotを越えて対応付けられるため、更新後の状態で同じprogramの検査結果を要求できます。 一方、typeやsymbolなど、特定のsnapshotから得たobjectを別のsnapshotのcheckerへ混ぜる契約ではありません。 IDによる対応関係と、型情報のobjectを共有してよい範囲を分ける設計です。
`ensurePrograms`には、新しいsnapshotで取得するprogramのIDを指定します。 snapshotの更新を重ねるだけの経路では、すべてのprogramを即座に再計算する必要がないためです。 `after.getProgram(beforeProgram.id)`で更新後のdiagnosticを取得するため、同じIDのprogramを確保します。 fileの変更を積み上げる操作と、programの型検査を要求する操作を別に扱います。 編集を続けるtoolでは、毎回すべての入力を最初から作る構成より、状態の遷移として表せる範囲が広がります([snapshot APIの更新とprogramの確保](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/#snapshots))。
`using`の宣言は、後で作ったresourceから先に破棄します。 `using`で宣言したprogramとsnapshotの破棄が終わると、最後に`API`を閉じます。 JavaScript側の変数がscopeを抜けた事実だけで、Go側が保持するresourceを即座に解放できるとは限らないため、明示した破棄の経路があります。 破棄後のsnapshotへ再び照会する使い方はできません。 更新前の状態を保持できる性質も、保持するresourceの生存期間内で使う契約です([explicit resource managementの破棄順序](https://github.com/tc39/proposal-explicit-resource-management))。
### virtual file systemの完全な置換と、layerによる上書き
`createFileSystem(files)`は、指定したfileを持つ完全なfile systemを定義します。 列挙していないfileは、明示してmountしない限り存在しません。 `createFileSystemWithLib(files)`は、同じ完全なfile systemへ、標準libraryのdirectoryを読み取り専用でmountします。 sourceの内容だけをmemory上で与えても、`Array`や`Promise`などの型宣言へアクセスする条件を用意できます。 hostのdiskへ一時fileを書き出す処理と、compilerへ見せるfile systemを定義する処理が、API上で分かれています([file system API](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/#file-system-apis))。
`createFileSystemLayer(files)`は、指定したfileだけを上書きし、残りをbaseのfile systemへ委ねます。 `createFileSystemLayer`へ渡すentryは、変更したsource fileの内容だけです。 標準libraryを全部書き直す必要がないのは、baseへ委ねる経路があるからです。 完全なfile systemを毎回置き換える操作と、変更部分だけを重ねる操作は、未指定のfileが存在するかの規則から異なります。 disk上のfileを消したのではなく、照会先のfile systemが返す状態を更新しています。
### ASTのupdateは、元のnodeを直接書き換えない
`item.before`を表すproperty accessのnodeへ、同じreceiverと同じ名前でupdateを適用すると、元のnodeを返します。 名前を`after`へ変えて更新すると、元とは異なるnodeが返ります。 元のnodeの名前は`before`に保たれ、新しいnodeだけが`after`になります。 変更がない場合の再利用と、変更がある場合の新しい構造の作成を、同じupdate APIで扱っています。
```typescript
import { NodeFlags } from "typescript/ast";
import {
createIdentifier,
createPropertyAccessExpression,
updatePropertyAccessExpression,
} from "typescript/ast/factory";
const receiver = createIdentifier("item");
const name = createIdentifier("before");
const original = createPropertyAccessExpression(receiver, undefined, name, NodeFlags.None);
const unchanged = updatePropertyAccessExpression(original, receiver, undefined, name);
const changed = updatePropertyAccessExpression(original, receiver, undefined, createIdentifier("after"));
console.log(unchanged === original, changed === original); // => true false
console.log(original.name.text, changed.name.text); // => before after
```
factoryの更新は、既存のnodeのpropertyへ代入して内容を変えるAPIとは異なります。 元の構文木を参照する処理が残っていても、新しいnodeを作っただけで元の名前が書き換わる結果にはなりません。 ただし、factoryで構文上のnodeを作ったことは、programの意味解析を更新した結果ではありません。 構文木の構築と、fileを更新してsnapshotのcheckerから型を取得する処理は別の入口です。 ASTの作成だけでは、更新後のsemantic diagnosticを計算した結果になりません([typescript/ast/factoryの位置付け](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/#typescript-71-api))。
## library宣言とconditional typeの検査も変わる
7.1 Betaは`target`と`lib`の`es2026`に対応します。 `Math.sumPrecise`、`Iterator.concat`、`JSON.rawJSON`、`Map`の`getOrInsert`などの宣言が対象です。 `esnext`には、まだproposalである`Promise.allKeyed`や`Iterator.zip`などの宣言も追加されています([7.1 Betaのlibrary宣言](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/#target-es2026))。
### ES2026の宣言があっても、runtimeへAPIは追加されない
7.1.0-betaの`--target es2026 --module nodenext`では、`Math.sumPrecise`の呼び出しを型検査できます。 しかしNode.js 26.12.0には、同じmethodの実装がありません。
```typescript
Math.sumPrecise([1e16, 1, -1e16]);
// TypeError: Math.sumPrecise is not a function
```
compilerがmethodの宣言を知っていることと、対象のJavaScript runtimeがmethodを実装していることは、独立した条件です。 出力にも`Math.sumPrecise(...)`という呼び出しが残り、補助実装へ置き換わってはいません。
同じruntimeでも、`Iterator.concat`と`JSON.rawJSON`は呼び出せます。
```typescript
console.log([...Iterator.concat([1, 2], [3])]); // => [1, 2, 3]
console.log(JSON.stringify({ value: JSON.rawJSON("9007199254740993") }));
// => {"value":9007199254740993}
```
`JSON.rawJSON`は、指定した数値の字句をJSONの値として保持します。 JavaScriptの`number`へ一度変換してから、丸めた値を文字列へ戻す経路ではありません。 入力には有効なraw JSONの値が必要であり、任意の文字列を無条件に認めるAPIではありません([JSON.rawJSONのECMAScript仕様](https://tc39.es/ecma262/#sec-json.rawjson))。
`Map.getOrInsert`は、keyが存在しないときに指定した値を挿入します。 同じkeyへ異なる値を渡しても、既存の値が優先されます。
```typescript
const counts = new Map();
console.log(counts.getOrInsert("key", 1)); // => 1
console.log(counts.getOrInsert("key", 9)); // => 1
let computed = 0;
console.log(counts.getOrInsertComputed("key", () => ++computed)); // => 1
console.log(computed); // => 0
```
既存の値を毎回上書きする`set`と同じ操作ではありません。 `getOrInsertComputed`は、すでにkeyが存在する場合、値を作るfunctionを呼びません。 値を先に計算してから渡す方式と、keyがない場合にだけfunctionを呼ぶ方式の違いが現れています([Map upsertの仕様](https://tc39.es/proposal-upsert/))。
`WeakMap`にも同じ名前のmethodの宣言が入り、`Uint8Array`にはBase64とhexの変換、`Array`には`fromAsync`などがES2026へ含まれます。 `lib`を選択する操作は、sourceが参照できる型宣言の集合を変えます。 `target`で古い構文へ変換しても、runtimeへlibraryのmethodを追加するわけではありません。 Node.js 26.12.0は`Iterator.concat`を提供しますが、`Math.sumPrecise`は提供しません。 7.1のES2026対応は、型宣言を安定した年次の選択肢へまとめる変更であり、すべてのruntimeへ同時に同じ機能を入れる変更ではありません。
型検査では、分配されるconditional typeの各分岐に対する制約が厳密になりました。 `B extends A`でも、`A`が`0 | 1`、`B`が`0`なら、`B`は分配後の`A = 1`を満たしません。 7.1は分配前の制約を、分配後のすべてのmemberにも成り立つ制約として扱わなくなります([分配されるtype parameterの検査](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/#stronger-checks-when-using-distributive-type-parameters))。
```typescript
type Contained = Part;
type Select =
Whole extends Part ? never : Contained;
type Counterexample = Select<0 | 1, 0>;
```
公式例の型名を変えた断片は、7.0.2では受け付けますが、7.1.0-betaではTS2344を出します。 `Whole = 0`の分岐は`never`になりますが、`Whole = 1`の分岐には`Contained<1, 0>`が残ります。 `0`は`1`を満たさないため、分配前の`Part extends Whole`だけでは後者の利用を正当化できません([公式発表の元の例](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/#stronger-checks-when-using-distributive-type-parameters))。
### 分配前のunionに対する制約は、各memberへの制約にならない
元の制約は、`Part`が`Whole`というunion全体へ代入できるという条件です。 `0`は`0 | 1`へ代入できますが、unionの各memberへすべて代入できるという条件は満たしません。 conditional typeの分配で`Whole`を0と1へ分け、元の包含関係を両方へコピーすると、1の分岐で誤った関係ができます。 7.1では、分配したparameterを、元のparameterより狭い別の型として扱います。 別のparameterとの関係を無条件には引き継ぎません。 診断は、型aliasの定義で要求した制約と、分配後に利用する制約の不一致を検出しています([分配されるconditional typeの規則](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types))。
修正する方法は、`Contained`へ渡したい型がunion全体なのか、分配したmemberなのかで異なります。 union全体を維持したい場合は、分配用に別の`Member`を用意できます。 `Member extends Part`で分岐しながら、`Contained`には分配していない`Whole`を渡す構成です。 `PreserveWhole<0 | 1, 0>`は、7.1でも値0を代入できる型になります。 分配して調べる対象と、包含関係を保証する対象を、同じparameter名へ重ねない修正です。
```typescript
type PreserveWhole<
Whole extends number,
Part extends Whole,
Member extends Whole = Whole,
> = Member extends Part ? never : Contained;
const preserved: PreserveWhole<0 | 1, 0> = 0;
```
一方、各memberについて本当に包含関係を要求したい場合は、分岐内で`Part extends Whole`を検査します。 条件を満たした分岐だけで`Contained`を使えば、分配後のmemberにも必要な包含関係が成立します。 `RequireEach<0 | 1, 0>`では、0の分岐も1の分岐も結果が`never`になります。 union全体を保存する修正とは、受け付ける分岐と結果の型が異なります。 warningを消すための同じ書き換えとして扱うと、元の型aliasが表したかった結果を変える場合があります。
`[Whole] extends [Part]`のようにtupleで囲む方法は、conditional typeの分配を止める別の変更です。 分配した各memberの結果を集める計算と、union全体を1回で判定する計算は異なります。 7.1の診断に対して、常にtupleで囲めば元の意味を保存できるという規則はありません。 型の分岐をmemberごとに残すのか、全体の条件へ変えるのかを、型aliasの入力と期待する結果で決めることになります。
### Python 3.15のlazy import、frozendict、起動処理の変更
URL: https://www.sakashita.page/python-3-15-release-notes-and-analysis/
Last updated: 2026-10-25T07:59:59.000Z
Python 3.15.0は2026年10月9日に公開されました。 10月21日には最初のmaintenance releaseである3.15.1も公開されています。 3.15.1は、同梱するpipの証明書bundleを更新するため、通常より早い日程で公開された版です([3.15.0の公開記録](https://www.python.org/downloads/release/python-3150/)、[3.15.1の公開記録](https://www.python.org/downloads/release/python-3151/))。
## `lazy import`はmoduleの実行を初回利用まで遅らせる
通常の`import`は、moduleを読み込む際にtop-levelのコードも実行します。 `lazy import`では名前をproxyに結び付け、名前が最初に使われた時点まで実際の読み込みを遅らせます。 起動時に宣言したmoduleのうち、使わない実行経路のmoduleを読み込まずに済む仕組みです([PEP 810](https://peps.python.org/pep-0810/))。
読み込む側の`payload.py`に、読み込み時の表示と値の定義を置きます。
```python
print("payload loaded")
VALUE: int = 42
```
呼び出す側の`lazy_demo.py`では、宣言後にmessageを表示してから`payload.VALUE`を読みます。
```python
lazy import payload
print("before first use")
print(payload.VALUE)
print(payload.VALUE)
```
CPython 3.15.0、macOS/aarch64でのstdoutは、名前の初回利用と読み込みの順序を表します。
```text
before first use
payload loaded
42
42
```
`lazy import payload`の行では`payload.py`が実行されず、最初の`payload.VALUE`で`payload loaded`が表示されます。 2回目の参照では読み込み済みのmoduleを使うため、top-levelの表示は繰り返しません。 読み込み時に行っていたplugin登録などの副作用も初回利用へ移るので、moduleを宣言しただけで登録が済む前提のコードは動作が変わります([PEP 810の実行時の意味](https://peps.python.org/pep-0810/))。

lazy importの宣言、別の処理、初回の名前参照、module実行、値の取得を順に示した図
読み込みが失敗する時点も変わります。 存在しないmoduleのlazy importは、初回利用時に例外を発生させます。 tracebackには利用箇所と元のimport箇所が含まれます。 全体へ適用する`-X lazy_imports=all`と、明示した`lazy`だけを尊重する既定値の`normal`も用意されています([Python 3.15のlazy import設定](https://docs.python.org/3.15/whatsnew/3.15.html#pep-810-explicit-lazy-imports))。
### 遅らせるのは名前の束縛後に行うmoduleの解決と実行
`lazy import`は、読み込んだmoduleの中で特定のfunctionの定義だけを省略する機能ではありません。 moduleの読み込み自体を名前の初回利用まで遅らせます。 `lazy from package import operation`でも、`operation`だけをmoduleから切り出して実行するのではなく、module全体を読み込み、要求した名前を解決します。 同じstatementで複数の名前をimportした場合は、名前ごとにproxyを持ちます。 先に使った名前でmodule全体を読み込んでも、未使用の名前はproxyの状態を保ちます。 proxyの置き換えは、名前を参照する時点で行います([lazy importの実行規則](https://docs.python.org/3.15/reference/simple%5Fstmts.html#lazy-imports))。
moduleを実際に解決する処理をreificationと呼びます。 reificationでは、通常のimportと同じく`__import__`を使いますが、参照する`sys.path`やimport hookはreification時点の状態です。 importを宣言した時点の探索経路を保存し、後から同じfileを必ず開く契約ではありません。 宣言と初回利用の間に探索経路を変更するプログラムでは、読み込みの対象を決める時点も移ります。 起動時の処理量に加え、moduleのtop-level codeと、読み込み済みmoduleを参照する処理の実行順序も変わります([PEP 810のreification](https://peps.python.org/pep-0810/#reification))。
通常のimportでは、moduleのtop-levelで設定した状態を、importの直後から使える場合があります。 たとえばdecoratorがplugin registryへfunctionを登録するmoduleなら、importを実行したことが登録の契機でした。 lazy importへ変えると、名前を宣言しただけの経路では登録が実行されません。 registryを読む処理がlazyなmoduleの名前を使わないまま進むと、moduleが登録するはずのentryを取得できません。 moduleの名前を直接使わない副作用に依存していた経路は、名前の初回参照という新しい実行条件と一致しない場合があります([PEP 810のimport時の副作用](https://peps.python.org/pep-0810/#observable-behavioral-shifts-opt-in-only))。
### 失敗したproxyは、次の参照で再試行する
`broken_lazy.py`のtop-levelで例外を送出すると、名前の参照ごとにmoduleの読み込みを再試行します。
```python
print("broken module entered")
raise RuntimeError("unavailable")
```
呼び出し側では、`lazy import`の宣言後に同じ名前を2回参照します。
```python
lazy import broken_lazy
print("import declared")
for index in range(2):
try:
print(broken_lazy.VALUE)
except RuntimeError as error:
print("failed", index, type(error).__name__)
```
```text
import declared
broken module entered
failed 0 RuntimeError
broken module entered
failed 1 RuntimeError
```
宣言だけではmoduleを実行せず、参照のたびに未解決のproxyから読み込みを始めます。 読み込みの失敗も、成功済みmoduleとしては保存しません。 失敗を初回だけで固定するmemoizationとは、再参照の結果が異なります。 moduleのtop-levelで例外の前に行った副作用も、再試行のたびに実行される可能性があります。 `broken_lazy`は、例外を送出するたびに同じmessageを再表示します。 外部への登録や書き込みを伴うmoduleでは、失敗前に実行した操作が残り得ます。 lazy importが失敗の時点を遅らせても、moduleの実行全体をtransactionとして巻き戻す機能を加えるわけではありません([PEP 810の失敗時のproxy](https://peps.python.org/pep-0810/#reification))。
明示した`lazy`を`try`の中へ置くことはできません。 `try: lazy import optional`という形でimportの成否を囲んでも、例外が発生するのは名前の利用時なので、囲んだ範囲と失敗する範囲が一致しなくなります。 3.15はmodule scopeの`lazy`だけを許可し、function、class body、`try`の中やstar importへの指定を拒否します。 function内に`lazy import`を置くと、parse時に拒否します。
```python
def load():
lazy import json
# SyntaxError: lazy import not allowed inside functions
```
functionを呼ぶ前にsourceの構文として拒否されるため、使わないfunction内なら許可されるという条件でもありません([lazy importの構文上の制限](https://docs.python.org/3.15/reference/simple%5Fstmts.html#lazy-imports))。
### 全体のmodeとfilterは、lazyにできるimportの範囲へ適用する
既定の`normal`は、明示的に`lazy`を付けたimportだけを遅延の候補にします。 `all`は通常のmodule-level importも候補にし、`none`は明示した`lazy`も含めて即時に読み込みます。 ただし、function内や`try`内、star importなど、lazyにできないimportの意味までは変わりません。 `all`でも、構文によって即時読み込みが残ります。 すべてのimportを無条件にproxyへ置き換える指定ではありません([PEP 810のglobal control](https://peps.python.org/pep-0810/#global-lazy-imports-control))。
modeは`PYTHON_LAZY_IMPORTS`、`-X lazy_imports=...`、`sys.set_lazy_imports()`で指定できます。 runtimeのAPIによる指定が最も優先され、command line、環境変数の順に続きます。 `sys.get_lazy_imports()`は現在のmodeを返します。 複数の場所で設定する場合、環境変数の値だけから、実行中のmodeを確定できるわけではありません。 設定の優先順位は、moduleごとのimport宣言とは別に、processの動作を決めます。
`sys.set_lazy_imports_filter`は、遅延の候補を絞るAPIです。 importする側のmodule名、import対象の名前、fromlistをfilterへ渡します。 たとえば読み込み時の副作用が必要なmoduleだけを、全体の`all`から外す構成に使えます。 filterは、構文やmodeで遅延の候補になっていないimportを、無条件にlazyへ変更する機能ではありません。 明示したimportの形、全体のmode、filterの結果を経て、実際にproxyを作るかが決まります。 library内の通常のimportと、process全体のmodeやfilterを変更する操作では、影響するscopeが異なります([PEP 810のlazy imports filter](https://peps.python.org/pep-0810/#lazy-imports-filter))。
複数threadが同じmoduleを初めて利用する場合には、既存のimport lockとmodule cacheを使って読み込みを調整します。 すべてのmoduleをthreadごとに別々に初期化する機能ではありません。 読み込みが完了すれば、通常のimportと同じmoduleを利用します。 循環importでは、早すぎる名前の参照を遅らせて循環を避けられる経路もありますが、初期化途中のmoduleから未定義の名前を要求する経路まで消えるわけではありません。 必要な名前へ実際に到達した時点の依存関係が、読み込みを完了できるかを決めます([PEP 810のthread safetyとreification](https://peps.python.org/pep-0810/#thread-safety-and-reification))。
## `frozendict`は対応関係を固定し、値の内部までは固定しない
組み込み型の`frozendict`は、作成後にkeyとvalueの対応を変更できないmappingです。 `dict`のsubclassではなく、`Mapping`として読み取り操作を提供します。 `dict`から作る場合は浅いコピーであり、登録したvalueのobjectまで複製するわけではありません([PEP 814](https://peps.python.org/pep-0814/))。
```python
source: dict[str, list[int]] = {"ports": [80]}
settings = frozendict(source)
source["extra"] = [443]
settings["ports"].append(8080)
print(list(settings), settings["ports"]) # => ['ports'] [80, 8080]
```
元の`dict`へ追加した`extra`は`settings`に現れませんが、共有しているlistの変更は現れます。 固定されるのはmappingの対応関係なので、設定全体を不変にしたい場合は、valueにもtupleなどの不変な値を使います([frozendictのshallow copy](https://peps.python.org/pep-0814/#copy))。
keyとvalueがhash可能なら、`frozendict`自体もhash可能です。 一方、listをvalueに持つ`settings`はhash可能にはなりません。 等価性とhashは挿入順に依存せず、`frozendict(a=1, b=2)`と`frozendict(b=2, a=1)`の比較結果は`True`です([frozendictのhashと比較](https://peps.python.org/pep-0814/#hashing-and-comparison))。
### 読み取り専用viewではなく、対応関係のsnapshotを作る
`types.MappingProxyType`も書き込みを許可しないmappingを提供しますが、元のmappingのviewです。 元の`dict`へ別の場所からkeyを追加すると、viewにも新しいkeyが現れます。 `frozendict(source)`は作成時の対応関係を固定するため、元の`dict`へ追加してもkeyは増えません。 初期状態が`{"a": 1}`の`dict`へ`b`を追加すると、snapshotとviewのkeyが異なります。
```python
from types import MappingProxyType
source = {"a": 1}
snapshot = frozendict(source)
view = MappingProxyType(source)
source["b"] = 2
print(list(snapshot)) # => ['a']
print(list(view)) # => ['a', 'b']
```
書き込み用のmethodを公開しない性質だけでは、外部からの変更が見えるかまでは同じになりません([PEP 814のcopy](https://peps.python.org/pep-0814/#copy)、[MappingProxyType](https://docs.python.org/3.15/library/types.html#types.MappingProxyType))。
浅いcopyなので、valueとして同じobjectを保存する関係は残ります。 `settings`から`ports`のkeyを削除したり、対応するlistを別の値へ置き換えたりはできません。 しかし、保存したlistへの`append`では、mappingの対応関係は変わりません。 設定のsnapshotがmutableなobjectを参照していれば、keyの集合を固定しても、参照先の内容は変化し得ます。 `frozendict`が提供する不変性の単位は、object graph全体ではなくmappingです。
既存の`frozendict`を浅くcopyすると、元のobjectを返します。
```python
import copy
left = frozendict(a=1, b=2)
print(copy.copy(left) is left) # => True
print(left.copy() is left) # => True
```
同じ対応関係の不変なmappingを浅くcopyする際、別の容器を作る必要がないためです。 ただし、valueがmutableな場合に内容を独立させる要求を、浅いcopyで満たすことはできません。 元の`dict`から初めて構築する操作と、すでにある`frozendict`を浅くcopyする操作では、独立させる対象が異なります。 同じcopyという名前でも、参照先まで再帰的に複製する操作を意味しません([PEP 814のcopy規則](https://peps.python.org/pep-0814/#copy))。
### 反復順序を保ちながら、等価性は対応関係で決まる
`frozendict`は挿入順を保って反復できます。 挿入順が異なる2個の`frozendict`でも、対応関係が等しければ等価性とhashは一致します。
```python
left = frozendict(a=1, b=2)
right = frozendict(b=2, a=1)
print(list(left)) # => ['a', 'b']
print(list(right)) # => ['b', 'a']
print(left == right) # => True
print(hash(left) == hash(right)) # => True
```
反復順序を保つ規則と、keyとvalueの対応で等価性を決める規則を、別に定義しています。 hashを使う容器へ入れた場合に、挿入順だけが異なる値を別のmappingとして扱う仕様ではありません([PEP 814のiterationとcomparison](https://peps.python.org/pep-0814/#iteration))。
hashを計算できる条件は、valueの型にも依存します。 listを含む`frozendict(ports=[80])`へ`hash`を適用すると、`TypeError`になります。 mappingへ書き込みできない事実だけから、任意のvalueのhashまで作れるわけではありません。 整数やtupleなど、hash可能な値から組み立てたmappingは、setの要素やcacheのkeyとして利用する条件を満たせます。 文字列などのhashはprocess間で同じ数値になる契約ではないので、hash値を保存して永続的な識別子にする用途とは異なります([PEP 814のhashing](https://peps.python.org/pep-0814/#hashing-and-comparison))。
### mergeは既存の値を変えず、結果のmappingを作る
`|`ではmappingをmergeできます。 右側のmappingとkeyが重複すれば、右側のvalueを結果へ採用します。
```python
left = frozendict(a=1, b=2)
merged = left | {"b": 3, "c": 4}
print(dict(merged)) # => {'a': 1, 'b': 3, 'c': 4}
print(isinstance(merged, frozendict)) # => True
print(dict(left)) # => {'a': 1, 'b': 2}
```
重複する`b`には右側の値が入り、左側の元の`frozendict`は`a=1, b=2`のままです。 `dict`を右側へ渡したからといって、既存の不変なmappingがmutableな容器へ変わる動作ではありません([PEP 814のunion operators](https://peps.python.org/pep-0814/#union-operators))。
`dict`のsubclassではないため、`isinstance(value, dict)`を前提にしたAPIでは、同じ型として受け取れません。 読み取り操作だけを要求するAPIなら、`Mapping`として扱う型の境界が対応します。 逆に、渡されたmappingへkeyを追加するAPIへ`frozendict`を渡せば、読み取り可能であるだけでは要求を満たしません。 新しい組み込み型を採用するときには、内容の型だけでなく、呼び出し先が変更操作を要求するかで互換性が変わります。 元の`dict`に対する変更を止めるだけのwrapperとして、無条件に置き換えられる型ではありません。
## `sentinel`で未指定と`None`を分ける
`sentinel`は、未指定を表す専用の値を作る組み込み型です。 `None`が有効な入力であるAPIでも、引数を省略した場合と区別できます。 単純な`object()`による代用と異なり、表示名、copy後の同一性、pickle後の同一性を扱う規則が用意されています([PEP 661](https://peps.python.org/pep-0661/))。
```python
MISSING = sentinel("MISSING")
def describe(value: str | MISSING | None = MISSING) -> str:
if value is MISSING:
return "omitted"
if value is None:
return "null"
return value
print(describe(), describe(None), describe("ready"))
```
出力は`omitted null ready`です。 判定には`is`を使うため、通常の文字列の内容と衝突しません。 moduleに定義したsentinelは、copyやpickleの往復でも同一性を維持します([sentinelの同一性と型](https://peps.python.org/pep-0661/#specification))。
### sentinelの名前は、通常の入力値と同じ内容を比較するための名前ではない
`sentinel("MISSING")`の表示は`MISSING`です。 単純な`object()`の表示に現れるaddressとは異なり、default argumentや診断へ表示したときにも、未指定を表す名前を読めます。 ただし、文字列`"MISSING"`が入力されても、`value is MISSING`には一致しません。 文字列の内容や、任意の入力の`__eq__`に依存せず、専用のobjectの同一性で未指定を判定します。 ユーザーが渡す文字列の集合と、未指定という状態が、同じ比較条件で衝突しない構成です([PEP 661のsentinelの表現と同一性](https://peps.python.org/pep-0661/#specification))。
`bool(MISSING)`は`True`です。 したがって、`if value:`は未指定の検査を代替しません。 `describe`の`None`は明示的にnullを渡す意味、`MISSING`は引数を省略する意味です。 さらに空文字列も有効な値なら、truthinessによる条件では空文字列と`None`を同じ分岐へ入れてしまいます。 `is MISSING`、`is None`、通常の値という分岐は、入力の状態を3種類に分けるAPIの契約を表します。
copyやpickleを通る処理では、未指定の判定に使うobjectが複製後も同じである必要があります。 moduleのtop-levelへ`MISSING`を束縛すれば、pickleの復元時も同じ名前からsentinelを解決できます。
```python
import copy
import pickle
from sentinellib import sentinel
MISSING = sentinel("MISSING")
print(bool(MISSING)) # => True
print(copy.copy(MISSING) is MISSING) # => True
print(copy.deepcopy(MISSING) is MISSING) # => True
print(pickle.loads(pickle.dumps(MISSING)) is MISSING) # => True
```
単に表示名だけを再現し、別のobjectを作る結果ではありません。 pickleが名前を解決できるよう、module内の名前として定義したsentinelを使っています。 任意のlocal変数へ一時的に作った値まで、別processから同じglobal名で解決できるという説明ではありません([PEP 661のserialization](https://peps.python.org/pep-0661/#specification))。
型の表記では、sentinel自身を型式の一部として使えます。 `str | MISSING | None`は、文字列、専用の未指定値、nullの集合を表しています。 通常の`object`という型だけへ広げると、未指定以外の任意のobjectまで型上の候補になりますが、sentinelの型を使えば専用の状態を残せます。 `is MISSING`で分岐した後の値をtype checkerが絞り込むための情報にもなります。 runtimeに専用の値を作ることと、annotationへ専用の状態を表すことを、同じ定義で接続する仕組みです([PEP 661のtype checking](https://peps.python.org/pep-0661/#typing))。
## comprehensionの各反復で展開する
comprehensionで`*`と`**`を使えるようになりました。 複数のlistを平坦化する場合、各反復で返すlistの要素を`*group`で展開できます。 mappingなら`**part`でkeyとvalueを展開し、重複するkeyには後から現れるvalueが入ります([PEP 798](https://peps.python.org/pep-0798/))。
```python
print([*group for group in [[1, 2], [3]]])
print({**part for part in [{"a": 1}, {"a": 2, "b": 3}]})
```
出力は`[1, 2, 3]`と`{'a': 2, 'b': 3}`です。 外側のlistの形を残す`[group for group in ...]`とは異なり、各反復が複数の要素を結果へ追加します。
### comprehensionの1反復が、0個以上の要素を出力する
`[*group for group in groups]`では、外側の反復が`group`を1個選び、選んだ値を展開します。 空の`group`なら要素を追加せず、2個の要素を持つ`group`なら2個追加します。 空のgroupを含む入力でも、出力するlistへ空の要素を追加しません。
```python
groups = [[1, 2], [], [3, 4]]
print([*group for group in groups]) # => [1, 2, 3, 4]
```
外側の反復回数と、出力する要素数が一致する必要はありません。 別の`for`を重ねて各要素を取り出す構成と同じ平坦化を、反復結果の展開として直接表せます([PEP 798のlist comprehension](https://peps.python.org/pep-0798/#specification))。
set comprehensionの展開では、追加する値がsetの要素として処理されます。 複数のgroupに同じ値があれば、重複はsetの規則で除かれます。 listの平坦化が順序付きで重複を保存することと、setへまとめることは、出力する容器の契約が異なります。 `sorted`で結果の集合を比較すれば、setの表示順へ依存せずに重複の除去を表せます。
```python
groups = [[1, 2], [2, 3], [4]]
print(sorted({*group for group in groups})) # => [1, 2, 3, 4]
```
展開する構文を共通に使えても、set自体へ挿入順の契約を追加する変更ではありません。
dict comprehensionの`**part`は、mappingのkeyとvalueを結果へ反映します。 function callのkeyword argumentでは、重複したkeyをerrorにする場面がありますが、dictを構築する展開では後のvalueが採用されます。 dict comprehensionの結果で`a`が2になるのは、2個目のmappingが`a`を上書きするからです。 `**`という同じ記号だけから、keyword argumentとdict構築の重複処理を同一視できません。 comprehensionへ追加した展開は、出力先のdictの構築規則に従います([PEP 798のdict unpacking](https://peps.python.org/pep-0798/))。
## UTF-8の既定化とpackage startupの変更
UTF-8 modeが既定で有効になり、明示的なencodingがないtext file、標準入出力、pipeのencodingに影響します。 locale固有のencodingを使う必要がある場合は`encoding="locale"`で指定できます。 `PYTHONUTF8=0`または`-X utf8=0`による無効化も残っています([PEP 686](https://peps.python.org/pep-0686/))。
たとえば従来の既定encodingで保存したファイルを`open(path)`で読むコードは、同じファイルでもUTF-8としてdecodeするようになります。 encodingを省略した読み書きが変更対象であり、すでに`encoding="utf-8"`や`encoding="cp932"`を指定した操作は指定したencodingを使います。 localeのencoding自体を調べる`locale.getencoding()`は、UTF-8 modeに左右されません([PEP 686の互換性とlocale API](https://peps.python.org/pep-0686/))。
### 既定のencodingを変えても、既存fileのbyte列は変わらない
UTF-8 modeは、encodingを省略した操作が選ぶ規則を変えます。 保存済みのfileを探してUTF-8へ変換する移行処理ではありません。 CP932でencodeしたfileをUTF-8として読むと、decodeに失敗する場合があります。 encodingを明示すれば、元の文字列へ復元できます。
```python
from pathlib import Path
from tempfile import TemporaryDirectory
import sys
with TemporaryDirectory() as directory:
path = Path(directory) / "legacy.txt"
path.write_bytes("日本語".encode("cp932"))
print(sys.flags.utf8_mode) # => 1
try:
with path.open() as stream:
stream.read()
except UnicodeDecodeError as error:
print(type(error).__name__) # => UnicodeDecodeError
print(path.read_text(encoding="cp932")) # => 日本語
```
入力のbyte列は同じでも、decodeする規則の違いが成功と失敗を分けます。
すでにUTF-8を既定にしていたlocaleでは、3.14以前から同じencodingを選ぶ場合があります。 version番号の更新だけで、すべての環境の読み込み結果が変わるわけではありません。 変更が現れるのは、以前の既定値がUTF-8と異なり、かつencodingを明示していなかった操作です。 `encoding="locale"`は、UTF-8 modeの既定値とは別にlocaleのencodingを選ぶ指定として残ります。 外部fileの形式がCP932だと分かっている場合には、localeの値へ依存する指定と、形式を明示する指定でも意味が異なります([PEP 686の互換性](https://peps.python.org/pep-0686/#backward-compatibility))。
`locale.getencoding()`は、UTF-8 modeを反映したtext I/Oの既定値を返すためのAPIではありません。 OSのlocale encodingを調べるため、UTF-8 modeでもlocaleの値を返します。 一方、text I/Oが使うencodingは、明示した引数、UTF-8 mode、標準streamの設定などから決まります。 localeが返した名前だけを使って、encodingを省略したfileの動作を再現する処理は、新しい既定値とずれる場合があります。 `PYTHONUTF8=0`や`-X utf8=0`は、modeを無効にするprocessの設定です([PEP 686のlocale APIの区別](https://peps.python.org/pep-0686/#locale-getencoding))。
起動処理には`.start`ファイルが加わりました。 既存の`.pth`が`sys.path`の拡張と`import`行の実行を兼ねていたのに対し、`.start`には`pkg.mod:callable`形式のentry pointを記述します。 起動時には対応するcallableを引数なしで呼び出します([PEP 829](https://peps.python.org/pep-0829/))。
同名の`.start`があれば、対応する`.pth`内の`import`行は実行されません。 `.pth`のpath拡張は維持されるため、pathの登録と起動時のコード実行を別々に移行できます。 3.15で`.pth`の`import`行が一律に動かなくなる変更ではありません([PEP 829のdiscoveryと互換性](https://peps.python.org/pep-0829/))。
### pathの収集を終えてから、起動時のentry pointを実行する
`.pth`の`import`行は、文字列を`exec()`へ渡して起動時のcodeを実行する形式でした。 `.start`は、moduleとcallableの名前を指定し、名前を解決して呼び出す形式です。 `startup_hooks:boot`なら、`startup_hooks`から`boot`を取得して引数なしで呼びます。 moduleだけの名前や、callableを省いた指定を、任意のPython sourceとして評価する形式ではありません。 fileの構文として許可する入口を、起動時のfunction呼び出しへ限定しています([PEP 829のentry point構文](https://peps.python.org/pep-0829/#entry-point-syntax))。
複数のsite directoryがある場合、起動codeが必要とするmoduleのpathが、後で処理するdirectoryの`.pth`に書かれている場合があります。 directoryを1個ずつ即時に処理する構成では、先のentry pointが実行される時点で、後のpathがまだ登録されていません。 3.15の`site.StartupState`は、複数directoryの設定を集め、path拡張を先に適用してからcodeを実行します。 pathの探索に必要な入力を揃える段階と、起動codeを実行する段階を分けるための状態objectです([site.StartupState](https://docs.python.org/3.15/library/site.html#site.StartupState))。
`site-a`に`demo.start`、呼び出すmoduleを別の`extra` directoryへ置く構成を考えます。 `extra`のpathは、後から読む`site-b/paths.pth`にだけ記載しています。 `state.addsitedir`によるdirectoryの登録だけでは、`startup_hooks`を読み込みません。 `state.process()`を呼ぶと、先にpathが有効になり、`demo.start`の`boot`を実行します。
```python
state = site.StartupState()
state.addsitedir(str(root / "site-a"))
state.addsitedir(str(root / "site-b"))
print("before", "startup_hooks" in sys.modules) # => before False
state.process()
# => new startup
print("after", "startup_hooks" in sys.modules) # => after True
```
`addsitedir`で収集した設定は、`process()`を呼ぶまで適用しません。 一方、`process()`は同じinstanceに何度呼んでも同じ結果になる操作ではありません。 再び呼ぶと、entry pointや旧式の`import`行をもう一度実行します。 設定を集めるAPIがあるからといって、処理済みかを自動で判定し、副作用を1回に抑える保証まで持つわけではありません。 1個の起動処理に対して収集を終え、実行を1回行う契約になります([StartupState.process](https://docs.python.org/3.15/library/site.html#site.StartupState.process))。
同じ`site-a`に旧式の`demo.pth`もある場合、同名の`.start`がcode実行の入口として優先されます。 同名の`demo.start`があるため、`legacy startup`というmessageは出力されません。 `.pth`内のpathの行まで無効になる規則ではなく、同名fileがある場合のcode実行の入口を`.start`へ移します。 pathの拡張とcodeの実行を、別のfileへ段階的に移せる条件です。 prefixはpackage名と必ず一致する必要はありませんが、対応する`.pth`と`.start`を結び付ける名前になります([PEP 829のfile discovery](https://peps.python.org/pep-0829/#file-naming-and-discovery))。
entry pointを実行して例外が出た場合は、errorを標準errorへ表示し、次のentry pointの処理を続けます。 構文として不正なentry pointはskipされ、読み込めないfileも後続のfileの処理へ進みます。 `.start`を置けば、起動時の任意の失敗がprocess全体の起動失敗になる、という契約ではありません。 fileをparseする際の失敗と、callableを実行した際の失敗には、報告の規則も違いがあります。 複数の起動処理を定義するpackageでは、前のentry pointが成功した状態だけを暗黙に前提とする依存関係に注意が必要です([PEP 829のerror handling](https://peps.python.org/pep-0829/#error-handling))。
`.start`のencodingはUTF-8で、BOMも許可します。 通常のtext fileのencodingを省略する操作とは異なり、startup file自体の形式として定義されています。 file名がdotで始まるものや、OSのhidden属性が付いたfileはdiscoveryから除外します。 directory内のfileはアルファベット順で処理します。 起動処理の順序は、file systemが返す列挙順だけでは決まりません([PEP 829のencodingとdiscovery](https://peps.python.org/pep-0829/#encoding))。
## profiling、typing、C extensionの変更
新しい`profiling` packageでは、tracingとsamplingのprofilerを別のnamespaceに整理します。 tracingはfunctionの呼び出しとreturnを計測し、samplingは時点ごとのstackから実行時間の分布を推定します。 Tachyonのsampling profilerは複数thread、async、free-threaded build、実行中processへのattachを扱います。 既存の`cProfile`は残り、pure Python実装の`profile`はdeprecatedです([PEP 799](https://peps.python.org/pep-0799/))。
### samplingは、呼び出し回数ではなく時点ごとのstackを集計する
`profiling.tracing`は、functionの呼び出しとreturnに沿って記録する方式です。 `profiling.sampling`は、実行中のstackを時点ごとに読み、頻繁に現れる処理を集計します。 短いfunctionがsampleの間で実行を終えれば、1回もstackに現れない場合があります。 したがって、sampleに現れた回数を、functionの正確な呼び出し回数として扱うことはできません。 新しいnamespaceの整理は、同じ測定値を別名で提供するためだけの変更ではありません([profiling packageの構成](https://peps.python.org/pep-0799/#new-module-structure))。
sampling profilerのcommandは、`python -m profiling.sampling run script.py`で対象を起動する形です。 `attach`は実行中processへ接続し、`dump`は現在のstackを取得し、`replay`は保存したbinary profileを別の形式へ変換します。 `run --live`は収集内容を対話的に表示する入口ですが、保存した集計値と同じく、sampleから得た分布として読む必要があります。 profile fileはsampleを採った処理と条件へ対応し、別のworkloadの高速化率を表しません([profiling.samplingのcommand](https://docs.python.org/3.15/library/profiling.sampling.html#command-line-interface))。
wall-clock、CPU、GILなどのmodeも、集計する条件が異なります。 I/Oを待つ時間を含むstackの分布と、CPUで実行している時間の分布は、同じ処理へ同じ割合を割り当てるとは限りません。 asyncな処理では、taskの関係を考慮してstackを再構成する機能もあります。 threadのstackだけを並べる場合と、論理的なtaskの連なりを追う場合で、遅延の原因を割り当てる対象が変わります。 tracingとsamplingの選択に加え、samplingのmodeも結果の意味を決める入力です([sampling profilerのmode](https://docs.python.org/3.15/library/profiling.sampling.html#profiling-modes))。
typingでは、`TypedDict`の既知のkey以外に入るvalueの型を指定する`extra_items`が追加されました。 `TypeForm`は`int | str`のような型を表す式の値を受け取るAPIを記述します。 `disjoint_base`は、無関係なdisjoint base同士を同時に継承できないとtype checkerへ伝えます([PEP 800](https://peps.python.org/pep-0800/))。
### `TypedDict`で既知のkey以外の型も表す
`TypedDict`は、keyごとに異なるvalueの型を表せます。 3.15の`extra_items`は、名前を宣言していないkeyのvalueへ型を指定するためのparameterです。 たとえば既知の`name`を`str`、追加のkeyの値を`int`とすれば、固定fieldと追加の数値fieldを同じmappingの型へ表せます。 単に`dict[str, object]`へ広げると、既知の`name`から取り出す型まで`object`になります。 `TypedDict`では既知のkeyの型を保存します([PEP 728のextra items](https://peps.python.org/pep-0728/#the-extra-items-class-parameter))。
`closed=True`は、追加のkeyを許可しない型を表します。 `total=False`のように、宣言済みのkeyを省略できるかを変える指定とは異なります。 追加keyの許可と、既知のkeyの必須性を同じ条件へまとめると、入力として受け取るmappingの集合が変わります。 `extra_items`に指定する型は、既知のkeyすべての型と同じである必要はありません。 追加fieldを型付けする機能なので、既知のfieldを一律に同じvalue typeへ制限する変更ではありません([PEP 728のclosedとtotality](https://peps.python.org/pep-0728/#interaction-with-totality))。
runtimeの`TypedDict`は、通常のdictを作るという性質を保ちます。 annotationを宣言しただけで、入力されたJSONの未知のkeyを実行時に削除したり、追加のvalueを検証したりはしません。 追加の型情報はtype checkerが扱う契約です。 外部入力を受け取るAPIでは、型として表現できる条件と、runtimeで検査した入力の条件を分ける必要があります。 3.15の型表現が増えた事実だけから、既存のvalidationの実装が不要になるわけではありません([PEP 728のruntime behavior](https://peps.python.org/pep-0728/#runtime-behavior))。
### `TypeForm`はclass objectより広い型式を受け取る
`type[T]`は、class objectを受け取るAPIを表す型です。 `TypeForm[T]`は、`int | str`や`list[int]`など、型式を評価して得た値まで扱います。 たとえばschemaから値を検証するfunctionが、受け取った型式に対応する型を返す場合、入力の型式と戻り値の型を同じ`T`で結び付けられます。 型式を単なる`object`として受け取る宣言では失われていた関係を、annotationに残すための機能です([PEP 747のspecification](https://peps.python.org/pep-0747/#specification))。
`TypeForm`は、型式を解釈してvalidationを実行する実装を提供するものではありません。 union、generic alias、literal typeなどを実際の値へ照合する処理は、利用するlibraryの役割として残ります。 同様に、annotationへ`TypeForm[T]`を書けば、任意のobjectを安全に`T`へcastできるという規則でもありません。 渡す型式の型と、検証後に返す値の型を結び付けるannotationです([PEP 747のconverterとassignability checker](https://peps.python.org/pep-0747/#typeform-use-cases))。
`disjoint_base`は、多重継承できる組み合わせへ別の情報を加えます。 CPythonには、MROやmetaclassの整合性とは別に、instance layoutの理由で同時に継承できないclassがあります。 非空の`__slots__`を持つclassや、多くのC実装の組み込み型が関係します。 type checkerは、無関係な2個のdisjoint baseを共通の派生型へ含められないと判断できます。 class宣言の不整合や、成立しない型の重なりを検出するための情報です。 単独のclassを一律に継承禁止へする`final`とは、制限する関係が異なります([PEP 800のlayoutとdisjoint bases](https://peps.python.org/pep-0800/#runtime-restrictions-on-multiple-inheritance))。
C extensionでは、free-threaded build向けのStable ABIである`abi3t`が追加されています。 従来の`abi3`のbinaryを無変更で使えるわけではありません。 objectのinstance structやmoduleのexport hookなどの修正が必要です。 対応できないextensionは、従来の`abi3`と、free-threaded build用のversion固有のABIを分けてbuildします([Python 3.15のStable ABI](https://docs.python.org/3.15/whatsnew/3.15.html#pep-803-stable-abi-for-free-threaded-builds))。
### `abi3t`では`PyObject`の内部layoutを固定しない
従来のStable ABIは、GIL-enabledなCPythonの複数のminor versionで同じextension binaryを使うための契約です。 free-threaded buildでは、reference countingなどに必要なobjectのlayoutが異なるため、従来のbinaryを無変更で適用する契約にはできません。 `abi3t`は`PyObject`をopaqueにし、直接のstruct layoutへ依存しないAPIでextensionを作ります。 内部layoutを将来変更できる範囲を残しながら、free-threaded buildでも複数versionへ対応する境界を作る方式です([PEP 803のStable ABI](https://peps.python.org/pep-0803/#stable-abi-for-free-threaded-builds))。
targetの指定には、既存の`Py_LIMITED_API`に加えて`Py_TARGET_ABI3T`を使います。 両方へ対応するextensionを明示的にbuildし、wheelのABI tagで`abi3.abi3t`と示す経路も定義されています。 tagの名前だけを変更して、従来のbinaryを両方へ対応したことにする移行ではありません。 sourceが使うAPIと、build時に選んだtarget ABIが条件になります。 Stable ABIが対象にする将来versionへの互換性と、異なるthreading build間の互換性を、別の軸として表します([PEP 803のtarget ABIの指定](https://peps.python.org/pep-0803/#choosing-the-target-abi))。
staticな`PyModuleDef`も移行に関わります。 従来のmodule定義には、`PyObject`を含むstructをstaticに置く形があり、opaqueなlayoutと両立しません。 新しいmodule export hookでは、moduleを構成するslotの配列を入口にでき、外側の`PyModuleDef`を必須にしません。 moduleを作る入口を内部objectの固定layoutから切り離す変更が、ABIの共有を妨げていた構造を取り除きます。 旧式の`PyInit_*`はsoft deprecatedで、3.15で直ちに削除される機能ではありません([PEP 793のexport hook](https://peps.python.org/pep-0793/#abstract))。
slotの表現も`PySlot`へ整理されます。 tagとflagsを持つunionにすることで、slotごとの型を区別し、将来追加されるslotを扱える形を用意します。 型の異なる値を、一律のpointerのように扱って渡す入口から、slotが要求する値を表現できる入口へ移します。 3.15で追加するmodule生成APIやexport hookも新しいslotを使います。 既存のslot structとAPIは、引き続き動くsoft deprecationの扱いです([PEP 820のslot system](https://peps.python.org/pep-0820/#abstract))。
実行環境側ではexperimental JITが更新され、frame pointerが既定で有効になりました。 公式Windows 64-bit binaryはtail-calling interpreterを使います。 公式macOS installerでは、free-threadingの構成が既定で入ります。 C APIにはbyte列を組み立てる`PyBytesWriter`が追加されました。 interpreterの終了処理に競合するthreadを扱う保護APIも加わっています([Python 3.15のC API](https://docs.python.org/3.15/whatsnew/3.15.html))。
### `PyBytesWriter`は構築途中の領域と完成した`bytes`を分ける
従来のC APIでは、`PyBytes_FromStringAndSize(NULL, size)`で未初期化の領域を持つ`bytes`を作り、後から内容を埋める方法がありました。 Pythonから見る`bytes`は不変ですが、構築途中には内容を変更する特殊な状態を作る方式です。 `PyBytesWriter`は、書き込み中のbufferを管理するobjectと、完成後に返す`bytes`を分けます。 出力sizeが増える場合のoverallocationも扱い、増えるたびに必要なsizeだけへ再確保する実装を避けられます([PEP 782の構築途中のobjectとallocation](https://peps.python.org/pep-0782/#rationale))。
writerは生成し、内容を書き込み、成功時にfinishして`bytes`を取得します。 失敗した経路ではdiscardして、構築途中のresourceを解放します。 bufferの成長後も、以前取得したpointerを使い続けられるとは限りません。 pointerを更新するAPIも用意されています。 完成した不変の値を外へ返す前に、可変な構築状態をwriterの生存期間へ閉じ込める契約です。 旧APIはsoft deprecatedで、warningを出して一律に拒否する移行ではありません([PEP 782のwriter API](https://peps.python.org/pep-0782/#api))。
### interpreterの寿命と、thread stateのattachを別に管理する
native libraryが作ったthreadからPythonへcallbackする場合、callbackの開始時点でinterpreterが終了処理へ入る競合があります。 単にinterpreterのpointerを保存していても、終了済みのinterpreterへattachしてよい保証にはなりません。 `PyInterpreterGuard`は、保持している間にinterpreterがfinalizationへ入らないようにするguardです。 複数threadがguardを保持でき、finalizationはguardがすべて閉じるまで待ちます。 終了が始まった後には新しいguardを取得できません([PEP 788のinterpreter guards](https://peps.python.org/pep-0788/#interpreter-guards))。
`PyInterpreterView`は、終了中または終了済みのinterpreterも参照できるopaqueなviewです。 viewからguardを取得する際に、まだ利用できるinterpreterかを判定します。 viewを保存すること自体と、interpreterの終了を止めるguardを保持することは同じ操作ではありません。 外部libraryへ長期間callbackを登録する構成なら、viewを登録情報として保存できます。 callbackの実行中だけguardを保持する構成とは、生存期間が異なります([PEP 788のinterpreter views](https://peps.python.org/pep-0788/#interpreter-views))。
guardで保護したinterpreterへthread stateをattachする入口が`PyThreadState_Ensure`です。 成功時のtokenを対応するreleaseへ返すことで、以前attachしていた状態へ戻します。 対象のinterpreterを保護する条件と、現在のOS threadに適切なthread stateを用意する条件を別に扱います。 guardを閉じ忘れるとfinalizationが永久に待つため、保護するAPIを呼んだ事実だけでは終了処理の健全性を保証しません。 取得と解放を成功経路と失敗経路の両方へ対応させることが、新しいAPIの契約に含まれます([PEP 788のthread stateのattachとdetach](https://peps.python.org/pep-0788/#attaching-and-detaching-thread-states))。
## 3.15.1は同梱pipの証明書bundleも更新する
3.15.1には約330件のbug fixが入り、同梱pipは26.2.2へ更新されました。 pipが使うcertifiは2026.10.19で、emSignのCAに関するincidentへの対応を含みます。 更新対象はinstallerに含まれるpipです。 既存のvirtual environmentのpipを自動更新する変更ではありません([Python 3.15.1の公開記録](https://www.python.org/downloads/release/python-3151/))。
### C# 15を.NET 11 RC2で動かす
URL: https://www.sakashita.page/csharp-15-dotnet-11-rc2-release-notes-and-analysis/
Last updated: 2026-10-25T07:00:00.000Z
.NET 11 RC2は2026年10月13日に公開されました。 2026年10月24日時点では正式公開前のSDKであり、C# 15の新機能を含んでいます。 `net11.0`を対象にするとC# 15が既定で選ばれ、memory safetyに関する追加機能には別途`preview`の指定が必要です([RC2の公式発表](https://devblogs.microsoft.com/dotnet/dotnet-11-rc-2/)、[C# 15の新機能](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-15))。
## unionは既存の型をcaseとして組み合わせる
`union`では、値として受け付けるcase typeを宣言します。 たとえば検索結果を`Found`か`Missing`に限る場合、共通のinterfaceを実装する代わりに、2つの型をunionへ登録できます。 case typeからunionへのimplicit conversionが生成されるため、呼び出し側で明示的な包装処理を書く必要はありません([unionの宣言と変換](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union))。
```csharp
public record Found(string Text);
public record Missing;
public union Lookup(Found, Missing);
```
`Lookup result = new Found("ready");`と代入した値は、type patternで中身を調べられます。 `Found found`が取り出すのはunion自体ではなく、保持している`Found`の値です。 `switch`の網羅性もcase typeを基準に検査されます([unionのpattern matching](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#union-pattern-matching))。
```csharp
static string Describe(Lookup result) => result switch
{
Found found => found.Text,
Missing => "not-found",
null => "empty"
};
Lookup result = new Found("ready");
Console.WriteLine(Describe(result)); // => ready
Console.WriteLine(Describe(default)); // => empty
```
SDK 11.0.100-rc.2.26504.105で実行すると、`Describe(new Found("ready"))`は`ready`を返します。 一方、`Describe(default)`は`empty`です。 生成されるunionは既定値の状態で中身が`null`になるため、case typeをすべて書いたことだけでは、既定値まで処理できるとは限りません([unionのnullと既定値](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#null-matching))。

FoundとMissingを受け取るLookupがswitchへ渡され、defaultの空状態もnull armへ分岐する図
生成されたunionは、値を`object?`の参照として保持します。 したがって`union Number(int, double)`のようにvalue typeをcaseにするとboxingが発生します。 unionを宣言しただけでallocationを避ける専用layoutになるわけではなく、独自の格納方式が必要な場合はcustom unionの実装が別に必要です([unionの格納方式](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#union-declarations))。
### 共通の継承関係を作らず、受け付ける型を宣言する
`Lookup`のcaseにした`Found`と`Missing`は、共通の基底classを持つ必要がありません。 既存のlibraryから受け取る型でも、unionのcaseとして列挙できます。 interfaceへmarkerを追加する方式では、対象の型の宣言を変更できるかが条件になりますが、unionは受け付ける側で型の集合を宣言します。 共通のmemberを呼ぶinterfaceと、結果の型を区別するunionでは、APIに表す情報が異なります。 unionを使う呼び出し側は、caseごとに別のmemberへアクセスできます([unionとinterfaceの役割](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union))。
caseにはclassやstructだけでなく、interface、type parameter、nullable type、別のunionも指定できます。 ただし、格納する値を`object`へ変換できる型である必要があります。 `Span`のような任意の値を、通常のreferenceとして保持できるわけではありません。 受け付ける型の集合と、値の保存に使えるmemory layoutには別の制約があります。 生成されたunionは単一の`object?`を保存するため、value typeの値にはboxingが伴います([case typeとして許可される型](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#union-declarations))。
整数42を`Number`へ変換すると、保持する値のruntime typeは`Int32`です。
```csharp
public union Number(int, double);
```
```csharp
Number number = 42;
Console.WriteLine(number.Value?.GetType().Name); // => Int32
```
`int`と`double`を同じ大きさのinline領域へ重ね合わせた結果ではなく、保持する値のruntime typeが`Int32`として残っています。 Cのunionのように、同じbyte列を別のfieldから読み直す意味の構文ではありません。 値の属するcaseを型として扱うための仕組みであり、格納済みの整数のbit列を浮動小数点数として再解釈するAPIにはなりません。
caseからのimplicit conversionは、対応するconstructorを呼ぶ処理です。 ただし、同じ型についてuser-definedなimplicit conversionがあれば、user-definedな変換を優先します。 複数のcaseが同じsourceの値へ同程度に適用できる場合には、compilerが都合のよいcaseを勝手に選ぶのではなく、曖昧さをerrorにします。 たとえばinterfaceをcaseへ含める構成では、sourceが複数のcaseを実装する可能性も型の集合の一部です。 列挙した型が互いに排他的であるという条件を、union宣言が自動的に作るわけではありません([union conversionの優先順位](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#union-conversions))。
### patternの形によって、中身を調べるか、union自身を調べるかが変わる
`result is Found`は、保持する値が`Found`かを調べます。 `result is Found { Text: "payload" }`なら、`Found`に一致した後、保持する`Found`の`Text`を調べます。 一方、`result is { Text: "wrapper" }`のように型を指定しないproperty patternは、union自身の`Text`を読みます。 `Lookup`自身の`Text`を`"wrapper"`、保持する`Found`の`Text`を`"payload"`にします。 異なる値への2個のpatternが、両方とも成立します([unionに対するpatternの適用対象](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#union-pattern-matching))。
```csharp
public union Lookup(Found, Missing)
{
public string Text => "wrapper";
}
```
```csharp
Lookup result = new Found("payload");
Console.WriteLine(result is Found { Text: "payload" }); // => True
Console.WriteLine(result is { Text: "wrapper" }); // => True
```
`var captured`は、中身を取り出すpatternではありません。 入力されたunion自身を変数へ束縛するため、`captured`を使って`Value`を読む経路になります。 discardの`_`も、case typeを絞り込む操作ではなく、入力全体へ一致します。 list patternの場合も、保持するcaseのindexerへ自動で切り替えるのではなく、union自身のcountやindexingのmemberを使います。 pattern matchingの構文を短くした結果、常に自動で中身を取り出すという説明では、property patternやlist patternの対象を取り違えます。
空のproperty patternである`{ }`は、特にnullとの違いが現れます。 nonnullableなstruct union自身は存在するので、`Lookup empty = default;`に対する`empty is { }`は`True`です。 一方、`empty is not null`はunionの中身を調べるため、結果は`False`です。 通常のreferenceに対して使うと似た条件に見える2つのpatternでも、unionへ適用した場合には同じ意味になりません。 中身の有無を条件にしたい処理で`{ }`へ書き換えると、既定値のunionを通過させる結果になります([空のproperty patternとnull pattern](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#union-pattern-matching))。
logical patternを組み合わせた場合にも、各patternの対象は保存されます。 `var value and not null`では、`var value`がunionを束縛し、`not null`が中身の有無を検査します。 型を指定した`Found and { Text: "payload" }`では、型の判定後の`Found`へproperty patternを適用します。 `and`を加えるだけで、後ろのpatternが必ずunionの中身へ変わるわけではありません。 前段の型patternが何を絞り込むかに応じて、後段が参照するmemberの対象も決まります。
### nullable wrapperがない状態と、空のunionを区別する
生成されるunionはstructなので、`Lookup?`にはnullable wrapperも加わります。 `HasValue`はnullable wrapperの有無を返しますが、unionのnull patternは内側の空の状態も受け付けます。
```csharp
Lookup? absent = null;
Lookup? wrappedEmpty = default(Lookup);
Console.WriteLine(absent.HasValue); // => False
Console.WriteLine(wrappedEmpty.HasValue); // => True
Console.WriteLine(absent is null); // => True
Console.WriteLine(wrappedEmpty is null); // => True
```
`wrappedEmpty`にはstructの値が存在していても、値が保持するcaseはnullです。 unionのnull patternは、nullable wrapperの不在だけでなく、中身がnullである状態も受け付けます([nullable unionのnull matching](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#null-matching))。
`Lookup[]`を作った直後の各要素も、structの既定値です。 すべてのcase typeをnonnullableに宣言していても、配列の初期状態で保持する値が自動的に`Found`や`Missing`になるわけではありません。 fieldや配列を通じて既定値が渡るAPIでは、caseの集合だけからruntimeの入力を復元できません。 `null` armを明示した`Describe`は、caseの成功状態と空の状態を別に処理します。 空の状態を`Missing`と同じ業務上の意味へ対応付けるかは、呼び出し側が定めるAPIの契約です。
nullable analysisが追跡する状態と、runtimeで到達し得るすべての状態も同じではありません。 case typeがすべてnonnullableなら、追跡上は中身がnot-nullとされ、`null` armの省略を必ずwarningにするわけではありません。 ただし、`default`による中身のnullは存在します。 したがって、warningがないことだけを、生成されたunionが空にならない証拠にはできません。 `default`が流れる経路を持つ場合には、`null` patternの意味を使って空の状態を受け取ります([unionのnullable analysisと既定値](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#union-exhaustiveness))。
### caseを追加したときに、処理漏れを残さないswitch
`Describe`から`Missing`のarmを削除すると、CS8509を出します。 warningsをerrorにするbuildでは、処理漏れがあるまま成功しません。
```csharp
static string Describe(Lookup value) => value switch
{
Found found => found.Text,
null => "empty"
};
// CS8509: Missing
```
`Found`と`null`を処理していても、宣言されたcaseの集合を満たしていないためです。 診断には不足したcaseの`Missing`が現れるため、nullの扱いとcaseの不足を区別できます。 caseが増えた場合も、個別のarmを列挙したswitchは、不足したcaseを検出する対象になります。
最後のarmを`_`へ変えると、将来加わるcaseも同じarmへ入ります。 空の状態だけを受け付けたい場合には、`null` armを使えば、宣言されていない別のcaseまで無条件に受け付ける動作を避けられます。 `_`を加えればwarningが消えることと、すべてのcaseを意図した処理へ割り当てたことは、異なる結果です。 新しいcaseをAPIの返り値へ追加したとき、switchの最後のarmによって、呼び出し元で受け付ける入力が変わります([caseの網羅性とnull arm](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#union-exhaustiveness))。
### custom unionでは、pattern matchingの読み出し経路も実装する
独自のclassやstructへ`UnionAttribute`を付ける場合は、caseごとの生成処理と、保持する値を返す`Value`を用意します。 単に属性を付けた型の任意のfieldを、compilerがcaseとして探す方式ではありません。 基本のpatternでは、1個の引数を取るpublicなconstructorのparameterからcase typeを定めます。 publicな`Value`は、`object?`として中身を返します。 生成されるunionとは異なる格納方式を選べますが、compilerが使う入口の形は規則で決まっています([custom unionの基本pattern](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#custom-union-types))。
value typeをboxingせず取り出す経路には、caseごとの`TryGetValue(out T value)`を使います。 中身の有無を調べる`HasValue`も加えられます。 type patternでは`TryGetValue`、null patternでは`HasValue`というように、対応するpatternの種類が異なります。 片方を実装すればもう片方の代わりにもなる、という規則ではありません。 `TryGetValue`がなければ、type patternで`Value`を使います。 `HasValue`がなければ、null patternで`Value`を使います([non-boxing access pattern](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#non-boxing-access-pattern))。
`Tagged`へ整数用field、bool用field、caseを示すbyteのtagを保存すれば、boxingせずに値を取り出す経路を定義できます。 `TryGetValue(out int)`はtagが整数なら成功し、`TryGetValue(out bool)`はboolなら成功します。 `Value`のgetterには、呼び出し回数を増やすcounterも置いています。
```csharp
[System.Runtime.CompilerServices.Union]
public struct Tagged : System.Runtime.CompilerServices.IUnion
{
private readonly int _integer;
private readonly bool _boolean;
private readonly byte _tag;
public static int ValueReads { get; private set; }
public Tagged(int value) { _integer = value; _tag = 1; }
public Tagged(bool value) { _boolean = value; _tag = 2; }
public bool HasValue => _tag != 0;
public object? Value
{
get
{
ValueReads++;
return _tag switch { 1 => _integer, 2 => _boolean, _ => null };
}
}
public bool TryGetValue(out int value) { value = _integer; return _tag == 1; }
public bool TryGetValue(out bool value) { value = _boolean; return _tag == 2; }
}
```
整数42から作った値を、`int`、`bool`、`null`のarmを持つswitchへ渡すと、整数用の`TryGetValue`を使います。 `Value`から`object?`を取得して型検査する経路は、実行されません。
```csharp
Tagged tagged = 42;
Console.WriteLine(tagged switch
{
int value => $"tagged={value}",
bool value => $"tagged={value}",
null => "tagged=empty"
}); // => tagged=42
Console.WriteLine(Tagged.ValueReads); // => 0
```
読み出し経路を自分で提供する場合には、各memberが同じ内容を表す必要があります。 `Value`が整数を返すのに、`TryGetValue(out bool)`が成功する実装なら、patternが選ぶcaseは矛盾します。 `HasValue`がfalseなのに`Value`がnon-nullを返す実装も、中身の有無を一貫して表せません。 compilerはcustom unionが契約を守る前提で網羅性や変換を扱います。 属性の宣言だけで、各memberの実装の一致まで検証するわけではありません([custom unionのsoundness](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#custom-union-types))。
structの既定値についても、custom unionは契約を定めます。 生成されたunionのように必ず空にする方式だけでなく、既定値を有効なcaseとして表す設計も可能です。 ただし、既定値に対する`Value`、`HasValue`、`TryGetValue`の結果を合わせなければなりません。 独自のlayoutを選ぶ利点と、defaultを含めた状態の解釈を実装する責任が、同じAPIの境界にあります。 `ValueReads`が数えるのはgetterの呼び出しであり、`Tagged`以外の処理を含むallocationの個数ではありません。
生成するunionのbodyへは、保持したcaseを使うmethodや計算したpropertyを追加できます。 一方、独自のinstance field、auto-property、field-like eventは追加できません。 格納方式を生成する構文へ、別のinstance状態を自由に混ぜる形ではなく、追加の状態が必要ならcustom union側で設計します。 publicで引数が1個のconstructorも、compilerがcaseごとに生成するconstructorと重なるため、自由に追加する入口ではありません。 独自に宣言するconstructorには、生成したconstructorへ直接または間接に委譲する`this(...)` initializerが必要です。 caseの生成経路を使わず、不整合な内部状態を作るconstructorを通常のbodyへ追加する構文にはなっていません([union bodyのmemberとconstructorの制限](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union#union-declarations))。
## `closed`はassemblyの外からの直接継承を止める
unionが既存の型を列挙するのに対し、`closed`は継承関係を使う型に適用します。 `closed`なclassは暗黙にabstractとなり、直接継承できる型を同じassemblyに限定します。 compilerが直接の派生型を把握できるため、派生型をすべて扱う`switch`にdefault armを書く必要がなくなります([closedの規則](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/closed))。
```csharp
public closed record State;
public sealed record Running(int Count) : State;
public sealed record Stopped : State;
```
派生型を列挙したswitchでは、絞り込んだ型のpropertyを読み取れます。
```csharp
State state = new Running(3);
Console.WriteLine(state switch
{
Running running => $"running={running.Count}",
Stopped => "stopped",
null => "missing"
}); // => running=3
```
ただし、継承の制限は自動で子孫すべてに及ぶわけではありません。 直接の派生型を通常の非sealed classにすると、外部assemblyが派生型をさらに継承できます。 末端の`Running`と`Stopped`は`sealed`です。 中間の階層まで網羅性を保つ場合は、中間の型も`closed`にします([closedの継承範囲](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/closed))。
### 直接の派生型を閉じても、子孫は自動では閉じない
library側に`closed class State`と通常の`class OpenState : State`があれば、外部assemblyは`OpenState`から継承できます。 外部projectから`ExternalState : OpenState`を宣言するbuildは成功しています。 一方、外部assemblyから直接`ExternalState : State`を宣言すると、CS9382になります。 同じ外部projectでも、直接の基底型が`State`か`OpenState`かによって、継承の可否が変わります。 `closed`は、assembly内で派生した後の型を、暗黙にすべて`sealed`へ変える指定ではありません([closedの直接継承の制限](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/closed#declaration-rules))。
`OpenState`を扱うtype patternは、外部assemblyで加わった`ExternalState`も受け付けます。 したがって、直接の派生型をすべて扱うswitchが網羅的であることと、末端のclass名をすべて知っていることは同じ条件ではありません。 直接の派生型が開いていても、派生型のpatternで子孫を覆います。 末端ごとに処理を分けたいAPIでは、中間の型も`closed`にして子孫を列挙するか、`sealed`で継承を止めます。 型の修飾子によって、許可する拡張の範囲が変わります([closed hierarchyのpattern規則](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-15.0/closed-hierarchies))。
`closed`なclassは暗黙にabstractです。 基底型のinstanceを直接生成できないため、派生型をすべて覆う条件から、基底型の値の集合を扱えます。 `closed abstract`のように同じ性質を重ねる指定や、`sealed`、`static`との組み合わせは許可されません。 `closed`をclassの修飾子以外でidentifierとして使う場合は、contextual keywordとして通常の名前にもなります。 宣言位置で意味が衝突する場合には`@closed`としてidentifierを明示できます([closedの宣言規則](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/closed#declaration-rules))。
genericな派生型にも、可能な型の集合を確定できる条件があります。 たとえば`Leaf : Tree`では、派生型のparameterが基底型の構成に現れます。 一方、`Constant : Tree`のように、基底型の構成に現れない`U`を追加する直接継承は許可されません。 `Tree`から見たときに、`U`へ何を代入した派生型が存在し得るかを基底型から決められないためです。 `closed`という修飾子だけでなく、genericな型の構成にも、網羅性を検査するための制約があります([closedとgenericな派生型](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/closed#declaration-rules))。
nullableな`State?`を受け取るswitchでは、派生型のほかにnullも入力になります。 直接の派生型をすべて書いても、nullableな基底型についてnullを覆ったことにはなりません。 unionの空状態と同じ構文で`null`を書けますが、`closed`なclassの場合は、reference自体がnullである条件です。 型の集合を閉じる機能と、nullを許す型の指定は、別の型情報として検査されます([closedのnullableなswitch](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/closed#exhaustive-switch-expressions))。
## collection expressionからcomparerを渡す
collection expressionの先頭に`with(...)`を書くと、元になるconstructorやfactoryへ引数を渡せます。 `HashSet`ならcomparerを指定でき、要素の同一性をcollection expression内で決められます。 要素を追加した後でcomparerを変更する処理ではありません([collection expression arguments](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/collection-expressions#collection-expression-arguments))。
```csharp
HashSet names =
[with(StringComparer.OrdinalIgnoreCase), "Ada", "ada"];
Console.WriteLine($"unique={names.Count}");
```
出力は`unique=1`です。 `OrdinalIgnoreCase`をconstruction時に渡したため、大小文字だけが異なる2つの要素は同一の値として扱われます。 `List`のcapacityのような引数にも使えますが、`with(...)`は任意の位置へ挿入する要素ではなく、先頭に置く引数指定です([collection expressionの引数と適用条件](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/collection-expressions))。
### collectionの型を決める段階と、constructorを選ぶ段階
`with(...)`の引数は、collection expressionのtarget typeが決まった後で、constructorやcollection builderの選択に使われます。 外側のmethodのoverloadを選ぶ際に、引数の内容を手掛かりとしてtarget typeを決める仕組みではありません。 `List`と`HashSet`を受け取る2個の`Use`がある場合、comparerを渡した事実だけで`HashSet`のoverloadへ確定しません。 comparerを引数に書いても、`Use`のtarget typeを決められません。 compilerは、曖昧な呼び出しを示すCS0121を出します([collection expression argumentsのoverload解決](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/collection-expressions#restrictions))。
```csharp
Use([with(StringComparer.OrdinalIgnoreCase), "one", "ONE"]);
// CS0121
```
`HashSet value = [...]`といった変数への代入なら、target typeは宣言から決まります。 代入先の型が決まった後に、comparerを受け取る`HashSet`のconstructorを選べます。 先に明示した型へ代入してから`Use(value)`を呼ぶ構成では、外側のoverloadにも引数の型が現れます。 引数を受け付けるconstructorを探して、外側のoverloadを逆算する規則ではありません。 type inferenceとconstructorの選択を別の段階として扱うため、負例は曖昧になります。
容量指定も、要素の型の推論を代替しません。 `Use(List values)`へ`[with(10)]`だけを渡しても、10はconstructorのcapacityであり、`T`を決める要素ではありません。 要素がない入力から`T`を推論できない場合には、`Use`のように型を指定する必要があります。 capacityの数値を`int`として書いたことから、collectionの要素も`int`になる規則ではありません([constructor引数とtype inference](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/collection-expressions#restrictions))。
容量と要素数は、同じcollectionの別のpropertyです。
```csharp
List reserved = [with(capacity: 8), 1, 2];
System.Console.WriteLine(reserved.Count); // => 2
System.Console.WriteLine(reserved.Capacity); // => 8
```
8個の要素を作って後ろ6個を既定値で埋める構文ではなく、2個の要素を入れる容器に容量の引数を渡しています。 arrayの長さを指定する構文とも異なります。 `with(...)`はarrayや`Span`、`ReadOnlySpan`をtargetにするcollection expressionでは使えません。 引数があるなら任意のcollection風の型へ適用できるという拡張ではなく、対応する構築経路を持つ型が対象です([constructor引数の適用対象](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/collection-expressions#constructor-arguments))。
collection builderを持つ型では、`with(...)`の引数がcreate methodへ渡ります。 要素をまとめた`ReadOnlySpan`は最後のparameterで、追加の設定用引数はspanより前です。 comparerを受け取るfactoryなら、比較規則と要素列を同じ生成処理へ渡せます。 通常のconstructorを呼ぶ型と、builderのcreate methodを呼ぶ型では、同じcollection expressionでも選ばれる入口が異なります。 compilerはtarget typeの構築規則に従って引数を割り当てます([collection builder arguments](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/collection-expressions#collection-builder-arguments))。
評価順序では、`with(...)`の引数を先に評価し、要素の式は左から右へ1回ずつ評価します。 ただし、spreadに渡す`GetItems()`の呼び出し時点と、返されたsequenceの列挙時点は同じとは限りません。 compilerは後ろの要素式を評価した後で、spreadを列挙する場合があります。 要素式を左から右へ評価する規則だけでは、iteratorの副作用まで同じ順で発生するとは言えません。 構築に渡す値の評価と、値を取り出す列挙を別の操作として扱います([collection expression argumentsの評価と列挙](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/collection-expressions#collection-expression-arguments))。
## extension indexerはreceiverの機能を呼び出す構文を増やす
C# 14のextension blockに、instance indexerを宣言できるようになりました。 receiverの値を使うため、extension blockには名前付きのreceiver parameterが必要です。 `IEnumerable`にindexerを加える場合は、取得処理を`ElementAt`へ委譲できます([extension indexer](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/extension))。
```csharp
public static class SequenceIndex
{
extension(IEnumerable values)
{
public int this[int index] => values.ElementAt(index);
}
}
```
追加したindexerは、通常のmemberと同じ呼び出し構文を持ちます。
```csharp
IEnumerable values = new[] { 10, 20, 30 };
Console.WriteLine(values[1]); // => 20
```
indexerの実装は`ElementAt`なので、元のsequenceを配列へ変換したり、結果を自動でcacheしたりしません。 indexerという構文を追加しても、呼び出される処理の内容はextension側に書いた実装のままです([extension memberの解決とreceiver](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/extension))。
### indexerの追加は、receiverの型を作り替える操作ではない
extension blockはreceiverを引数として受け取り、追加のmemberを呼べるsource上の入口を定義します。 既存の`IEnumerable`というinterfaceへ新しいinstance memberを追加する変更ではありません。 interfaceを実装する既存の型へ、indexerの実装を追加し直す必要はありません。 逆に、extensionを宣言した事実だけで、元の型のmetadataに同じindexerが現れるという動作でもありません。 呼び出し元のscopeで利用できるextensionと、receiver自身が持つmemberをcompilerが解決します([extension declarationとreceiver](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/extension))。
`IEnumerable`をreceiverにするindexerでは、要素を列挙して指定位置へ進む実装も定義できます。 `values[1]`の見た目がarrayと同じでも、arrayの直接参照へ自動で変わる契約ではありません。 `yield return`を使うsequenceでは、indexerを呼ぶたびに、先頭から必要な要素を列挙します。
```csharp
int steps = 0;
IEnumerable Sequence()
{
for (int index = 0; index < 4; index++)
{
steps++;
yield return index * 10;
}
}
IEnumerable sequence = Sequence();
Console.WriteLine(sequence[2]); // => 20
Console.WriteLine(sequence[2]); // => 20
Console.WriteLine(steps); // => 6
```
`ElementAt(2)`は各呼び出しで3要素を要求するため、2回分の列挙でcounterは6になります。 indexerを追加する構文には、列挙した値を保存するcacheは含まれません。
`ElementAt`の実装は、receiverが持つ別のindexing経路を使える場合もあります。 したがって、`IEnumerable`というstatic typeだけでは、すべての呼び出しの列挙回数を確定できません。 `Sequence()`のiteratorはindexerを持たず、要素を得る経路は`yield return`による列挙です。 新構文ではextension indexerの呼び出しを表現できます。 任意のsequenceに対する一定時間のrandom accessを保証する拡張ではありません([Enumerable.ElementAtの動作](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.elementat?view=net-11.0))。
getterへ書いた処理が例外を送出すれば、indexer経由でも同じ処理の失敗を受けます。 負のindexや範囲外のindexに対する規則も、extension indexerを宣言しただけでは別の規則に変わりません。 たとえば範囲外を既定値として扱うAPIへ変えるなら、getterの実装を変更する必要があります。 呼び出す構文を増やす変更と、失敗時の返り値や計算量を変える変更は、実装上の変更箇所が異なります。 元の型へ追加できない操作を、元の型のinstance操作に似た構文で公開できることが、extension indexerの範囲です。
## 入れ子のloopから名前で移動先を選ぶ
`break`と`continue`に、外側のloopなどに付けたlabelを指定できるようになりました。 内側のloopで条件を満たした時点で、外側のloopの次の反復へ進む場合は`continue outer;`と書けます。 従来のようにflagを保存し、loopを抜けた後でflagを再検査する必要がなくなります([labeled jump statements](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/jump-statements))。
```csharp
int visits = 0;
outer:
for (int row = 0; row < 2; row++)
{
for (int column = 0; column < 3; column++)
{
visits++;
if (column == 1)
continue outer;
}
}
Console.WriteLine($"visits={visits}"); // => visits=4
```
各行で`column`が0と1のときだけ訪問し、1に達すると外側の`for`の更新処理へ進みます。 labelなしの`continue`なら内側のloopを続けるため、2行とも`column`が2まで進み、訪問回数は6になります。
### labelへの`goto`と、label付きloopへの`continue`は移動先が異なる
`continue outer`は、labelが付いたloopの継続処理へ進みます。 `outer:`という行へ戻って、`for`の初期化をもう一度実行する操作ではありません。 `continue outer`では`row++`を実行し、外側の継続条件を検査してから次の行へ進みます。 `goto outer`へ置き換えると、labelが付いたstatementの入口へ移動するため、同じ`row`の更新規則を表すことにはなりません。 移動先の名前を指定する点は似ていても、loopを継続する構造を保持しているかが異なります([labeled continue](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/jump-statements#the-continue-statement))。
`break outer`なら、labelを付けたloop全体の後ろへ進みます。 内側の`switch`から外側のloopを終了する場合も、通常の`break`が`switch`だけを終了する動作と区別できます。 `continue`の対象はloop、`break`の対象はloopまたは`switch`であり、任意のstatement名を同じように指定できるわけではありません。 対象はjumpを含む外側の構造である必要があるため、関係のない後続statementへ移動する`goto`の代替構文ではありません([labeled break](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/jump-statements#the-break-statement))。
label付きのjumpでも、抜ける範囲にある`finally`は実行されます。 外側の各反復で`try`へ入り、内側の2個目の要素で`continue outer`を実行すると、各行の終了時に`finally`を実行します。 各行の途中から次の行へ進むたびに、`try`を抜ける終了処理が実行されています。 labelで外側のloopを選ぶことによって、途中の終了処理を飛び越えて消してしまう機能ではありません([jumpとtryの終了処理](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/statements#131010-the-continue-statement))。
複数のloopを抜けるために保存していたflagは、以前の反復の状態を後の条件が読むための値です。 `break outer`を使う構成では、終了条件を満たした位置から、対象のloopを直接終了できます。 ただし、flagを後段の処理でも結果として使っていた場合には、jumpへ置き換えるだけで同じ返り値や後処理を保存できるとは限りません。 control flowだけを表していたflagと、検索結果などの値を運んでいたflagでは、置き換える対象が異なります。 `visits`は要素の訪問回数を数える値で、loopの継続先は`outer`のlabelが指定します。
## pointerの保持とmemoryへのアクセスを分けるpreview
memory safetyのpreviewでは、pointer typeの宣言や`&`によるaddressの取得だけなら、`unsafe` contextを必要としなくなります。 `fixed`、pointerへの`stackalloc`、unmanaged typeの`sizeof`も対象です。 一方、`*p`、`p[i]`、`p->member`、function pointerの呼び出しは、指しているmemoryへアクセスするため`unsafe` contextが必要です([pointerの規則](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code))。
新しい`unsafe(expression)`は、式1つに`unsafe` contextを設けます。 field initializerのようにblockを置けない位置でも使えます。 新構文とpointerの規則には`LangVersion`の`preview`と、`AllowUnsafeBlocks`の有効化が必要です([C# 15のmemory safety preview](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-15#memory-safety))。
```csharp
int number = 42;
int* pointer = &number;
System.Console.WriteLine(unsafe(*pointer)); // => 42
```
RC2のprojectでも、両方の設定が必要です。 pointerの宣言とaddressの取得は通常のcontextに置き、値を読み出す`*pointer`だけを`unsafe(...)`で囲んでいます。
さらに`updated-memory-safety-rules`を有効にすると、`unsafe`を付けたmemberの呼び出し側にも`unsafe` contextを要求します。 assemblyには適用した規則を示す`MemorySafetyRulesAttribute`が記録されます。 `safe` contextual keywordは、`extern` memberや特殊なlayoutのfieldに安全性の契約を表します([memory safetyの規則](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code))。
### pointerを保存できる範囲と、pointerを使って読む範囲
従来の規則は、pointer typeの宣言と、指しているmemoryの読み出しの両方に`unsafe` contextを要求していました。 C# 15のpreviewでは、pointerの存在と、pointer経由のmemory accessを分けています。 `int* pointer = &number`はaddressを保持するだけなので通常のcontextへ置けますが、`*pointer`は参照先を読む操作なので`unsafe`が必要です。 同じpointer変数を扱っていても、値を保持する操作とdereferenceする操作の境界がsourceへ現れます([updated modelのrelaxed operations](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#relaxed-operations))。
`fixed`を通常のcontextで使える変更も、pinした領域への任意の読み書きをsafeにする変更ではありません。 managedな配列をpinし、先頭addressを保存する処理は通常のcontextへ置けます。 pinした後の`p[i]`はpointer経由のelement accessなので、引き続き`unsafe` contextが必要です。 pinningはGCによる移動を止める条件であり、indexが範囲内であることまでcompilerがpointerから復元する保証ではありません。 操作の許可を細かくした結果、`fixed`のscope全体へ一律に`unsafe`を広げる必要が減ります。
`unsafe(...)`を省いたpointerのdereferenceには、RC2のcompilerもCS9360を出します。
```csharp
Console.WriteLine(*pointer); // CS9360
```
pointerの宣言とaddressの取得は受け付けられ、dereferenceを含む出力の行だけが拒否されています。 `LangVersion=preview`を指定していても、memory accessの許可まで省略されるわけではありません。 逆に、`AllowUnsafeBlocks`はunsafeな構文をcompileするためのproject設定であり、個々の式のcontextを自動的にunsafeへ変える設定ではありません。 projectが構文を受け付ける条件と、式が置かれたcontextの条件を両方満たす必要があります。
`stackalloc`をpointerへ代入する操作と、`Span`へ代入する操作も、型としての契約は残ります。 pointerへした値には、参照先の長さをindexingで検査する仕組みが付いていません。 previewで宣言のための`unsafe`が不要になっても、`Span`が持つ長さの情報までpointerへ移るわけではありません。 同じstack上の領域を使っていても、アクセスに使う型によって、保持する情報と許可する操作が異なります([pointer typeとstack allocation](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#pointer-types))。
### `unsafe(expression)`は括弧の中だけへcontextを作る
`unsafe(*pointer)`は、括弧内のdereferenceへ許可を限定します。 外側のmethod全体や、同じstatementに含まれる別の式へまでcontextを広げません。 field initializerやconstructor initializer、`catch`の`when`句のように、通常のblockを置けない位置でも式を囲めます。 1個のoperationだけを監査対象として示し、周囲の通常の処理からsource上で区切る構成が可能です([unsafe expressions](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#unsafe-expressions))。
`await unsafe(CreateTask())`なら、unsafeなcontextはtaskを作る呼び出しだけです。 `await`はunsafe contextの外にあります。 method全体をunsafeにしてasyncな処理をまとめて包む形とは、contextの範囲が異なります。 ただし、括弧で囲んだ事実が、呼び出すoperationの安全性を検証する証拠になるわけではありません。 source上で許可する範囲を明示する構文と、実際にpointerの寿命や境界が成立する条件を分けて扱います([unsafe expressionとawaitの位置](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#unsafe-expressions))。
### `unsafe`なmemberの契約を、呼び出し側へ伝える
`updated-memory-safety-rules`を有効にした規則では、memberに付ける`unsafe`と、body内の`unsafe`が異なる役割を持ちます。 signatureの`unsafe`は呼び出し側に条件を課し、body内のblockやexpressionは実際のmemory accessを許可します。 signatureへ付けたmodifierだけで、body全体がunsafe contextになる従来の読み方とは異なります。 呼び出し元が守る条件をAPIの宣言へ表し、実装内部で許可するoperationの範囲は別に記述します([requires-unsafe members](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#requires-unsafe-members))。
projectの`Features`へ`updated-memory-safety-rules`を指定すると、呼び出すmemberの契約も適用されます。 `public static unsafe int Read(int* pointer)`のbodyは、`return unsafe(*pointer);`としています。 呼び出す側にも`unsafe`が必要で、省略すると`Native.Read(int*)`にCS9362を出します。
```csharp
int number = 42;
int* pointer = &number;
Console.WriteLine(unsafe(Native.Read(pointer))); // => 42
```
```csharp
public static class Native
{
public static unsafe int Read(int* pointer)
{
return unsafe(*pointer);
}
}
```
呼び出す側がpointerを直接dereferenceしていなくても、memberの宣言にある契約が呼び出し位置へ伝わっています。
APIを通常のcontextから呼べる形へ公開したい場合は、実装側で条件を満たすboundaryを作ります。 たとえばmanagedなbyte配列を受け取る実装です。 nullを検査して配列をpinし、長さ未満のindexだけを読めば、呼び出し元へ未検証のpointerを要求せずに値を計算できます。 内部に`unsafe`があっても、signatureを`unsafe`にせず、内部の検査とscopeで条件を満たす構成です。 反対に、任意の外部pointerを受け取り、有効な長さや寿命を検証できないAPIでは、長さや寿命の保証が呼び出し元へ残ります。 新しい規則は、内部で使う構文と、外部へ残る条件の違いを宣言へ反映します([requires-unsafeの条件を伝播または解消する規則](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#discharge-requires-unsafe-obligations))。
assemblyには、適用したmemory safetyの規則を示すattributeを保存します。 libraryとconsumerが別々にcompileされる場合、consumerは呼び出すmemberの契約をmetadataから判断する必要があります。 source fileにある`unsafe`という表記だけでは、別のassemblyをcompileする側へ、modifierに適用する規則が伝わりません。 `MemorySafetyRulesAttribute`は、規則のversionをassemblyへ記録するための仕組みです。 既存のassemblyすべてへ新しい呼び出し条件を無条件に適用する説明では、異なる規則でcompileされた境界を表せません([updated rulesの有効化](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#enable-the-updated-memory-safety-rules))。
`safe`は、任意のunsafeな実装へ付ければ検証済みになる一般的なmodifierではありません。 `extern`のmemberや、explicitまたはextended layoutのfieldなど、規則が指定する位置で使うcontextual keywordです。 native側の実装や、memory layoutの重なりに関する条件を引き受ける境界が関係します。 通常のpointerのdereferenceへ`safe`を加えて、`unsafe` contextを省略する構文ではありません。 pointerを保存する許可、memoryへアクセスする許可、APIの呼び出し契約を、各構文が別々に扱っています([memory safetyのsafeの適用範囲](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code))。
## 古いruntimeで使えるstatic interface memberの範囲
C# 15では、default interface implementationに未対応のruntimeでも、non-virtualなstatic interface memberを宣言して使えます。 interface自身に属するhelperを置き、`IMeasurement.ToMeters(...)`のように直接呼ぶ使い方が対象です。 instanceのdefault implementationには、引き続きruntimeの対応が必要です。 `static abstract`、`static virtual`、protectedなmemberも対象外です([runtimeの要件](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface#static-interface-members-and-runtime-support))。
non-virtualなstatic memberをinterface名から直接呼ぶ処理では、instanceの実装を探索するdispatchを必要としません。 interfaceに置いたhelperの本体を、特定の型名から呼び出せます。 一方、`static abstract`を使うgenericな処理では、type argumentに応じた実装を解決するruntimeの機能が関係します。 同じstaticという表記でも、固定したhelperの呼び出しと、型ごとに異なる実装を選ぶ操作を区別する必要があります。 C# 15が許可範囲を広げるのは、前者のnon-virtualなmemberです([static interface memberのruntime条件](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface#static-interface-members-and-runtime-support))。
default interface implementationを使うinstance memberは、既定の本体をinterfaceから利用する動作にruntimeの対応が必要です。 non-virtualなstatic memberを宣言できるようになった事実から、既存のinstanceの既定実装も同じtargetへ持ち込めるとは言えません。 language versionを15へ上げることと、runtimeが実装するdispatchの機能を増やすことは別の変更です。 libraryの公開APIを作る場合、helperをinterfaceの名前にまとめたい要求と、実装型へ契約を課したい要求で、使うmemberの種類が変わります。
### Java 27の9つのJEPと、既定動作の変更
URL: https://www.sakashita.page/java-jdk-27-release-notes-and-analysis/
Last updated: 2026-10-25T06:00:00.000Z
JDK 27は2026年9月15日にGAとなりました。 9つのJEPが含まれ、compact object headers、G1、TLS 1.3の鍵交換では既定動作が変わっています。 primitive patternなどの言語機能と一部のAPIはpreview、Vector APIはincubatorとして提供されています([OracleのJDK 27リリースノート](https://www.oracle.com/java/technologies/javase/27-relnote-issues.html))。
## object headerを96 bitから64 bitへ
HotSpotのcompact object headersが、64-bit環境で既定になりました。 従来の96-bitのheaderを64 bitにまとめる変更であり、objectごとに保持する管理情報を減らします。 たとえば小さいobjectを大量に持つheapではheaderの割合が大きいため、objectの内容を変更せずに管理領域を縮められます([JEP 534](https://openjdk.org/jeps/534))。

従来の96-bit headerを64-bit headerへまとめ、fieldの領域は別に維持する図
96 bitから64 bitへの変更は、アプリケーション全体のheapが3分の1減るという意味ではありません。 field、array、alignmentによるpaddingもobjectのsizeを決めるためです。 従来のlayoutも残っており、`-XX:-UseCompactObjectHeaders`で無効にできます([JEP 534の適用範囲と切り替え](https://openjdk.org/jeps/534))。
Temurin 27+35、macOS/aarch64の起動flagでは、compact object headersとG1が有効です。
```sh
java -XX:+PrintFlagsFinal -version 2>&1 | rg 'UseCompactObjectHeaders|UseG1GC'
```
```text
bool UseCompactObjectHeaders = true {product lp64_product} {default}
bool UseG1GC = true {product} {ergonomic}
```
G1も、JDK 27から環境による既定GCの違いを減らす変更の対象になっています。 以前は小規模な環境などでSerial GCを選択していました。 JDK 27ではG1が全環境の既定です([JEP 523](https://openjdk.org/jeps/523))。
### class pointerをheaderの中へ収める仕組み
従来のHotSpotのheaderは、mark wordとclass wordに分かれていました。 mark wordはGCのobject age、lockの状態、identity hash codeなどを保持し、class wordはobjectのclassを識別します。 64-bit環境でcompressed class pointerを使う構成なら、mark wordが64 bit、class wordが32 bitで、合計96 bitです。 compact object headersではclass pointerの圧縮表現をmark wordへ組み込み、合計64 bitへ収めます。 単にclassを識別する情報を削除したわけではありません([JEP 450の従来layoutとcompact layout](https://openjdk.org/jeps/450#Current-object-headers))。
classの情報は、method呼び出し、型検査、reflection、GCがobjectのsizeを求める処理にも必要です。 class pointerをmark wordと共有するlayoutでは、lockやGCによるheaderの書き換え後もclassを識別できなければなりません。 従来のstack-lockingはheaderをstack上のcopyへのpointerで上書きするため、class情報まで同じ場所へ詰め込む方式とは両立しませんでした。 compact layoutに対応するlock処理は、header全体の上書きへ依存せず、状態を示すbitを使います([JEP 450のlocking](https://openjdk.org/jeps/450#Locking))。
GCでobjectを移動するときにも、移動前の位置から新しい位置を示すforwarding情報が必要です。 objectをcopyできなかった場合に、元の位置を自分自身へのpointerとしてheaderへ書く方式では、class情報を失います。 compact layoutではself-forwardedな状態を専用のbitで示し、class情報を保存します。 GCがheaderの表現へ依存するため、headerを4 byte短くする変更は、allocation時のsize計算だけでは完結しません([JEP 450のGC forwarding](https://openjdk.org/jeps/450#GC-forwarding))。
JDK 27で新しくなったのは、主に有効化の既定値です。 compact object headersはJDK 24でexperimentalとして導入され、JDK 25でproduct featureになっています。 JDK 27では、従来必要だった`-XX:+UseCompactObjectHeaders`を省略しても使われます。 一方、古いlayoutは削除されておらず、`-XX:-UseCompactObjectHeaders`を指定した比較も可能です。 headerの表現を前提とする診断やnative連携では、JDKのversionだけでなく、実際に選択したlayoutが入力になります([JEP 534の導入経緯と切り替え](https://openjdk.org/jeps/534#Description))。
### object全体のsizeはalignmentによって変わる
headerを12 byteから8 byteへ短くしても、すべてのobjectが4 byte小さくなるとは限りません。 object全体のsizeには、fieldの配置とalignmentに合わせたpaddingが含まれます。 たとえば8 byte境界へ丸めるlayoutでは、headerとfieldの合計が16 byteへ収まるobjectは、headerの短縮前後で同じ16 byteになる場合があります。 逆に合計が境界をまたぐobjectでは、4 byteのheader短縮が8 byteのsize差になる場合があります。 アプリケーションが保持するobjectの型の分布によって、合計の差も変わります。
`Instrumentation.getObjectSize`で返るのは、指定したobjectのshallow sizeです。 Java agentから`Instrumentation`を受け取り、同じ型のinstanceを2種類のlayoutで比較できます。 `SizeAgent.java`には、JVMがagentの起動時に呼ぶ`premain`を置きます。
```java
import java.lang.instrument.Instrumentation;
public class SizeAgent {
private static Instrumentation instrumentation;
public static void premain(String arguments, Instrumentation supplied) {
instrumentation = supplied;
}
public static long size(Object value) {
return instrumentation.getObjectSize(value);
}
}
```
`Size27.java`は、fieldのない型、整数fieldを1個持つ型、配列を同じ入口へ渡します。
```java
class Size27 {
static class Empty {}
static class OneInt { int value; }
static class OneLong { long value; }
public static void main(String[] args) {
System.out.println("empty=" + SizeAgent.size(new Empty()));
System.out.println("one-int=" + SizeAgent.size(new OneInt()));
System.out.println("one-long=" + SizeAgent.size(new OneLong()));
System.out.println("int-array-one=" + SizeAgent.size(new int[1]));
}
}
```
agentのmanifestは、`premain`を持つclassを指定します。 `agent.mf`の内容は、末尾の改行を含む次の1行です。
```text
Premain-Class: SizeAgent
```
Temurin 27+35、macOS/aarch64でのcompileと起動は、次のcommandに対応します。
```sh
javac SizeAgent.java Size27.java
jar --create --file size-agent.jar --manifest agent.mf SizeAgent.class
java -javaagent:size-agent.jar Size27
# => empty=8
# => one-int=16
# => one-long=16
# => int-array-one=16
java -XX:-UseCompactObjectHeaders -javaagent:size-agent.jar Size27
# => empty=16
# => one-int=16
# => one-long=24
# => int-array-one=24
```
fieldのない`Empty`では8 byte、`OneInt`では0 byte、`OneLong`では8 byteの差になります。 header自体の差は4 byteでも、objectの末尾をalignmentへ丸めた後の差は一定になりません。
たとえば、示したlayoutの`Empty`、`OneInt`、`OneLong`を1000個ずつ保持する場合を考えます。 object自身のsizeを足すと、従来layoutは56000 byte、compact layoutは40000 byteです。 3000個すべてへ一律に4 byteの差を掛けた12000 byteと異なり、差は16000 byteになります。 `OneInt`は縮まず、残る2種類は8 byteずつ縮むためです。 保持する型が`OneInt`だけなら、同じ個数でもobject自身のsizeは変わりません。 headerの短縮幅と、型ごとのpaddingを含む差を分ければ、型の個数から占有量を計算できます。 当該合計には、objectを参照する配列やcollection自身の領域を含めていません。 containerまで含むheapの差を求める場合は、containerの要素数とlayoutも加算の入力になります。
配列には、headerに加えて長さの情報と要素の領域があります。 要素が1個の`int[]`も、compact layoutで16 byte、従来layoutで24 byteになります。 要素数を増やした場合に、要素の値が圧縮される機能ではありません。 参照先のobjectまで加算するdeep sizeではなく、配列object自身の占有量です。 `getObjectSize`は実装依存の近似値を返すAPIです。 測定値にはJVMとalignment設定の条件が含まれます([Instrumentation.getObjectSize](https://docs.oracle.com/en/java/javase/27/docs/api/java.instrument/java/lang/instrument/Instrumentation.html#getObjectSize%28java.lang.Object%29))。
### G1の既定化が変えるのは、明示していなかったGCの選択
以前のHotSpotは、CPU数や利用可能なmemoryからGCを選んでいました。 server向けの環境ではG1を選ぶ一方、単一CPUや物理memoryが1792 MB未満の環境ではSerialを選択する構成がありました。 同じcommand lineでも、配置先の資源制約によって異なるGCになる場合がありました。 JDK 27では、GCを明示しない場合の選択をG1へ統一します([JEP 523の従来の選択条件](https://openjdk.org/jeps/523#Motivation))。
CPU数とheapを制限した起動でも、GCの選択はG1です。
```sh
java -XX:ActiveProcessorCount=1 -Xmx64m -XX:+PrintFlagsFinal -version 2>&1 \
| rg 'UseG1GC|UseSerialGC'
```
```text
bool UseG1GC = true {product} {ergonomic}
bool UseSerialGC = false {product} {default}
```
CPUを1個として扱い、最大heapを64 MBへ制限した起動でも、G1が選ばれています。 GCの指定を含まないcontainerの起動設定では、既定化の対象が以前のserver環境より広くなります。 すでに`-XX:+UseSerialGC`などを指定していた起動は、既定値だけに依存する起動とは異なります。 Serialは削除されず、明示した選択の経路は残ります([JEP 523の適用条件](https://openjdk.org/jeps/523#Description))。
JEP 523では、G1の同期処理、throughput、native memory使用量などの改善を、既定化の理由に挙げています。 ただし、G1とSerialは同じ回収algorithmではありません。 headerの短縮によるheap使用量の減少と、GCの選択によるpauseや回収処理の変化も、異なる変更です。 JDK 27への更新でGC回数に差が出た場合には、layoutとcollectorの同時変更も比較の条件に含まれます。 `getObjectSize`は経過時間を返さないため、同じsizeの差からcollectorのpauseやthroughputの差は求められません。
## TLS 1.3でhybrid key exchangeを優先する
JDK 27のTLS 1.3実装は、`X25519MLKEM768`を既定のnamed groupの先頭に置きます。 従来型のX25519と、耐量子暗号のML-KEM-768を組み合わせるhybrid key exchangeです。 `javax.net.ssl`を使い、named groupを独自に限定していないアプリケーションでは、対応する接続先とのhandshakeで利用できます([JEP 527](https://openjdk.org/jeps/527))。
clientが送る最初のkey shareは`X25519MLKEM768`と`x25519`です。 接続先がhybrid方式に未対応でも、共通して対応するgroupを使って交渉します。 追加される`SecP256r1MLKEM768`と`SecP384r1MLKEM1024`は既定では有効にならず、`jdk.tls.namedGroups`などで選択します。 鍵交換の変更であり、証明書の署名方式を一括で置き換える変更ではありません([JEP 527のnamed groupと既定の順序](https://openjdk.org/jeps/527))。
### hybrid方式でも、handshakeの相手との共通groupを選ぶ
TLS 1.3のclientは、最初のmessageで対応するnamed groupとkey shareを送ります。 JDK 27の既定の優先順位では、最初が`X25519MLKEM768`、続いて`x25519`、`secp256r1`、`secp384r1`、`secp521r1`です。 さらに`x448`と、2048、3072、4096 bitのFFDHE groupが続きます。 hybrid方式だけに限定する変更ではなく、対応する接続先との交渉で優先する方式を追加する変更です([JEP 527の既定のgroup一覧](https://openjdk.org/jeps/527#Using-hybrid-key-exchange-schemes))。
key shareと、対応するgroupの一覧は同じ情報ではありません。 対応するすべてのgroupについて、最初からkey shareを送るわけではなく、既定ではhybridの`X25519MLKEM768`と従来の`x25519`を送ります。 handshakeの方式は、接続先との交渉で決まります。 JDK 27へ更新しても、hybrid方式に未対応の接続先との通信には従来方式を使います。 接続先の実装と設定も共通のgroupを決める条件になります。 従来方式を受け入れる相手との接続経路も、既定のgroup集合には残っています。
hybrid方式は、ECDHEとML-KEMの両方を組み合わせて鍵を導出する方式です。 従来型の方式だけ、またはML-KEMだけへ置き換える方式とは構成が異なります。 JEP 527は、ML-KEMだけを使うnon-hybrid方式を今回の対象に含めていません。 同じML-KEMという名前が現れても、TLSのnamed groupとして何を交渉するかが実際の機能です。 証明書が示すidentityや、署名algorithmの選択を一括して変更する指定ではありません([JEP 527の対象外となる方式](https://openjdk.org/jeps/527#Non-Goals))。
アプリケーションが独自にgroupを選ぶ場合には、`SSLParameters.setNamedGroups`へ名前を渡し、socketへ設定を戻します。 たとえば`new String[] { "X25519MLKEM768", "x25519" }`なら、hybrid方式と従来方式の2個を選ぶ指定です。 `SecP256r1MLKEM768`や`SecP384r1MLKEM1024`は実装に加わりましたが、既定で有効なgroupではないため、名前が存在することだけでは交渉候補へ含まれません。 process全体の既定には`jdk.tls.namedGroups`を使います。 接続ごとの設定には`SSLParameters`を使います([SSLParameters.setNamedGroups](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/javax/net/ssl/SSLParameters.html#setNamedGroups%28java.lang.String%5B%5D%29))。
すでに`jdk.tls.namedGroups`を固定している配置では、JDK側の新しい既定一覧を使いません。 たとえば従来のgroup名だけを列挙した設定を残せば、更新したJDKでもhybrid方式は候補に増えません。 反対に、hybrid方式だけを選ぶ設定は、従来方式も残す既定設定と同じ互換性の条件ではありません。 JDK 27が変更する既定順序と、アプリケーションが上書きしているgroup集合を合わせた結果が、実際のhandshakeの入力になります。
## primitive patternで値を失わない変換を判定する
5回目のpreviewとなるprimitive patternでは、`instanceof`や`switch`のpatternにprimitive typeを使えます。 単にcastできるかではなく、対象の値を正確に表せるかを判定します。 `double`から`int`への判定なら、整数として表せる`42.0`と、小数部分を失う`42.5`で結果が変わります([JEP 532](https://openjdk.org/jeps/532))。
```java
double exact = 42.0;
double fraction = 42.5;
System.out.println("int=" + (exact instanceof int)); // => int=true
System.out.println("fraction=" + (fraction instanceof int)); // => fraction=false
```
対象はTemurin 27+35で、compile時と実行時の両方でpreviewを有効にする必要があります。 明示的なcastの`(int) 42.5`なら小数部分を切り捨てますが、patternの判定は値を失う変換を成功扱いにしません。 判定に成功した値を変数として束縛する`instanceof int value`も使えるため、変換可否の判定と値の取り出しを同じ条件に置けます([JEP 532のexact conversion](https://openjdk.org/jeps/532))。
断片をclassの`main`に置き、`Release27.java`として保存すると単一ファイルで実行できます。 実行commandは`java --enable-preview --source 27 Release27.java`です。 class fileへ分ける場合は`javac --enable-preview --release 27 Release27.java`でcompileします。 実行時にも`java --enable-preview Release27`でpreviewを有効にします。
### 小数部分だけでなく、範囲と浮動小数点の値の区別も残す
`double`から`int`への変換では、小数部分だけでなく、型の範囲と特殊値の規則も判定に含まれます。 `int`の最大値を越える`2147483648.0`も、整数の形をしていても`int`で表せないため、patternに一致しません。 同じprimitive patternを、範囲と特殊値へ適用すると条件の差が現れます。
```java
double[] numbers = {42.0, 42.5, -0.0, Double.NaN,
Double.POSITIVE_INFINITY, 2147483648.0};
for (double number : numbers) {
System.out.println("exact=" + number + "/" + (number instanceof int));
}
```
```text
exact=42.0/true
exact=42.5/false
exact=-0.0/false
exact=NaN/false
exact=Infinity/false
exact=2.147483648E9/false
```
`-0.0`は整数の0へ変えると負のzeroという性質が失われるため、単なる切り捨ての可否とは違う結果になります([JEP 532のexact conversionと特殊値](https://openjdk.org/jeps/532))。
明示的なcastは、変換先の型へ値を作る操作です。 一方、primitive patternは、元の値を失わずに変換できる条件を満たすかを調べ、成功した場合に値を束縛します。 したがって、`(int) value`を先に実行して元の値と比べる処理を、無条件にpatternへ置き換えられるわけではありません。 特に無限大やNaN、符号付きzeroなどでは、通常の整数の範囲検査だけでは変換の意味を表し切れません。 値を拒否する条件をpatternへ含められることが、単に短いcast構文を増やす変更との違いです。
判定に使う式のstatic typeも動作を決めます。 入力の静的な型が`double`か`Object`かによっても、同じ数値の判定結果が異なります。
```java
double primitive = 42.0;
Object boxed = Double.valueOf(42.0);
System.out.println(primitive instanceof int); // => true
System.out.println(boxed instanceof int); // => false
```
数値として42でも、`Object`から任意のwrapperを取り出して任意のprimitiveへ変換する判定ではありません。 primitiveからのexact conversionと、referenceからwrapperを扱う規則では、許可する変換経路が異なります。 static typeを含めてpatternの適用規則が決まります([JEP 532の型ごとの規則](https://openjdk.org/jeps/532))。
`switch`へ適用する場合には、caseに一致する条件へexact conversionの条件が入ります。 狭いprimitive typeへ正確に収まる値を先に処理し、残りを広い型のcaseで処理する構成ができます。 入力を狭い型へcastしてから`switch`する構成では、caseへ入る前に情報を失いますが、patternでは成功条件をcase側に置けます。 ただし、先のcaseが後のcaseの入力をすべて覆う組み合わせには、従来のpattern switchと同じくdominanceの制約があります。 caseの並びは、入力を受け取る条件の優先順位を表します。
## `LazyConstant`は初回の計算結果を固定する
`LazyConstant`は、値を必要とする初回の`get()`で計算し、以後は同じ内容を返すpreview APIです。 `Supplier`のように呼ぶたびに計算するのではなく、複数threadが同時に初期化しようとしても、計算を担当するthreadは1つに限られます。 3回目のpreviewとしてJDK 27に含まれています([JEP 531](https://openjdk.org/jeps/531)、[LazyConstantのAPI](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/lang/LazyConstant.html))。
JDK 27では、初期化状態を調べる`isInitialized`と代替値を返す`orElse`が削除されました。 collectionには`Set.ofLazy`が加わり、`List`、`Set`、`Map`の遅延初期化を表現できます([JEP 531の変更点](https://openjdk.org/jeps/531))。
```java
static final LazyConstant CONFIG = LazyConstant.of(() -> {
System.out.println("initialize");
return "ready";
});
```
`CONFIG.get()`の結果を2回表示すると、出力は`initialize`、`ready`、`ready`の順になります。 `initialize`は計算するlambda内にあるため、2回目の`get()`では表示されません。 値は`LazyConstant`から強く参照され続け、内容が固定された後に値を追い出すcacheではありません([LazyConstantの初期化と到達可能性](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/lang/LazyConstant.html))。
計算が例外を送出した場合は失敗状態になり、後続の`get()`で計算を再試行しません。 `null`を返す計算や、自分自身の`get()`を呼ぶ再帰も失敗として扱います。 一度だけ初期化するAPIなので、一時的な通信失敗から自動で再試行する用途には別の制御が必要になります([LazyConstantのException handling](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/lang/LazyConstant.html#exception-handling))。
### 失敗した計算も、初期化の結果として固定する
initializerが例外を送出すると、最初の`get()`には元の例外をcauseに持つ`NoSuchElementException`を返します。 後続の`get()`も`NoSuchElementException`になりますが、同じinitializerを再実行せず、causeも引き継ぎません([LazyConstantの失敗時の契約](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/lang/LazyConstant.html#exception-handling))。
```java
var attempts = new AtomicInteger();
var failed = LazyConstant.of(() -> {
attempts.incrementAndGet();
throw new IllegalArgumentException("unavailable");
});
for (int index = 0; index < 2; index++) {
try {
failed.get();
} catch (Exception error) {
System.out.println(error.getClass().getSimpleName()
+ "/" + (error.getCause() != null));
}
}
System.out.println(attempts.get());
```
```text
NoSuchElementException/true
NoSuchElementException/false
1
```
initializerが`null`を返した場合も、値のない成功として保存するAPIではありません。 `NullPointerException`が失敗の原因になり、値を取得する呼び出しは失敗します。 初期化の途中で同じ`LazyConstant`の`get()`を再帰的に呼ぶ場合には、`IllegalStateException`が原因になります。 たとえば設定Aの計算が設定Bを要求し、設定Bの計算が再びAを要求する依存関係では、単に初期化が遅延されるという説明だけでは不十分です。 値を固定する順序に循環があれば、取得できる値を完成できない経路になります。
値の取得に成功した後は、同じ`LazyConstant`から同じ値を取得します。 複数のvirtual threadが同じ`get()`を呼んでも、共有する初期化の結果を受け取ります。
```java
var initialized = new AtomicInteger();
var lazy = LazyConstant.of(() -> {
initialized.incrementAndGet();
return new Object();
});
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var first = executor.submit(lazy::get);
var second = executor.submit(lazy::get);
System.out.println(first.get() == second.get()); // => true
}
System.out.println(initialized.get()); // => 1
```
initializerの中で毎回`new Object()`を使っていても、呼び出し元ごとに新しいobjectを作る動作にはなりません。 値の共有と、計算を1回に限る制御をAPIが担当します。 一方、戻したobject自体がmutableなら、内容の変更はobject側の契約に従います。
初期化中の`get()`は、別のthreadが担当する計算を待つ場合があります。 待つ側の呼び出しにtimeoutやcancelを渡すAPIではありません。 initializerの中に外部I/Oを置けば、I/Oの完了まで値の取得も完了しません。 遅延初期化は、外部I/Oのtimeoutまで追加する仕組みではありません([LazyConstantのthread safety](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/lang/LazyConstant.html))。
`static final`なfieldへ`LazyConstant`を置く構成には、JVMが固定された値の取得を最適化しやすい利点があります。 ただし、`get()`が初回にも必ず単なるfieldのloadになるとは限りません。 初回にはinitializerを呼ぶ経路があり、失敗状態も表します。 通常の`static final` fieldは、classの初期化時に値を計算します。 `LazyConstant`は、最初の`get()`までinitializerを実行しません([JEP 531の最適化と遅延初期化](https://openjdk.org/jeps/531))。
### collection全体を作る時点と、各要素を作る時点
`List.ofLazy(size, computingFunction)`では、listの長さを先に決め、各indexの要素は利用時に計算できます。 要素を計算するfunctionへcounterの加算を置くと、容器の構築と要素の生成を区別できます。
```java
var calls = new AtomicInteger();
var values = List.ofLazy(3, index -> {
calls.incrementAndGet();
return "item-" + index;
});
System.out.println("created=" + calls.get() + "/" + values.size()); // => created=0/3
System.out.println(values.get(1)); // => item-1
System.out.println(values.get(1)); // => item-1
System.out.println(calls.get()); // => 1
System.out.println(values); // => [item-0, item-1, item-2]
System.out.println(calls.get()); // => 3
```
要素の個数は3と分かっていますが、要素を作るfunctionはまだ呼ばれていません。 listという容器の形を確定させることと、内容の生成を完了させることを分けられます([List.ofLazy](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/util/List.html#ofLazy%28int,java.util.function.IntFunction%29))。
`get(1)`を繰り返しても、index 1の要素を再計算しません。 同じindexの要素を使うたびに再計算するviewではありません。 list全体の表示には、まだ初期化していなかったindex 0と2の値も必要になります。 表示のために全要素へアクセスすると、まだ計算していなかったindexも計算されます。 初期化を遅らせたcollectionでも、内容を列挙する操作を早い段階で呼べば、必要な計算は列挙時へ移ります。
遅延listはunmodifiableなlistです。 後から要素を追加し、削除し、別の要素へ置き換える用途にはなりません。 遅延なのは要素を計算する時点であり、collectionの構造を自由に変更できるという意味ではありません。 戻す要素はnullを許さず、同時に複数threadが利用する場合も、要素の遅延初期化をAPIが管理します。 値を固定した後に外側の入力が変わっても、要素を自動で作り直す仕組みではありません([Listのunmodifiableなfactoryの契約](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/util/List.html#unmodifiable))。
`Map.ofLazy`ではkeyの集合と、keyから値を計算するfunctionを渡します。 `Set.ofLazy`では候補の集合とpredicateを渡すため、値を作るlistと同じ引数構成ではありません。 JDK 27が加える`Set.ofLazy`を、任意のsupplierの配列からsetを作るAPIとして説明すると、実際の宣言と一致しません。 setに含める要素を遅延して決める操作と、indexからlistの要素を作る操作では、factoryの契約が異なります([JEP 531のcollection factory](https://openjdk.org/jeps/531))。
以前のpreviewで使えた`isInitialized`や`orElse`を削除した変更は、既存のpreview利用コードのsource互換性へ直接影響します。 初期化済みの状態を見て処理を分岐するcodeは、単にJDKを差し替えるだけではcompileできなくなります。 値が必要なら`get()`で初期化の結果を取得する形へ、取得を避けたいなら値を要求しない処理へ、呼び出し側の契約を分ける変更になります。 `isInitialized`の返り値で処理を分岐していたsourceには、初期化状態を公開しないAPIへ合わせた制御の変更が必要です([JEP 531のJDK 26からの差分](https://openjdk.org/jeps/531))。
## structured concurrencyでは`join()`の例外型まで表現する
7回目のpreviewとなるstructured concurrencyでは、scope内で起動したsubtaskの終了をscopeの生存期間内に収めます。 `fork`で起動し、`join`で結果を受け取り、`close`で全threadの終了を待つ流れです。 scopeを閉じた後までsubtaskを残す使い方にはなりません([StructuredTaskScopeのAPI](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/util/concurrent/StructuredTaskScope.html))。
```java
try (var scope = StructuredTaskScope.open(
StructuredTaskScope.Joiner.allSuccessfulOrThrow())) {
scope.fork(() -> "profile");
scope.fork(() -> "orders");
System.out.println(scope.join());
}
```
`join()`の返り値は、起動した順序に対応する`[profile, orders]`です。 `allSuccessfulOrThrow`は全subtaskの成功結果を集め、失敗したsubtaskがあればscopeをcancelして`ExecutionException`を送出します。 cancelで他のthreadにinterruptを要求しても、`close`はthreadが実際に終わるまで待ちます。 interruptに応答しない処理があれば、scopeの終了も遅れます。 cancelは強制終了ではありません([StructuredTaskScopeの終了](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/util/concurrent/StructuredTaskScope.html))。
JDK 27では`StructuredTaskScope`と`Joiner`に、`join()`から送出する例外の型を示す3個目のtype parameter、`R_X`が加わりました。 例外を別の型へ変換するfactory overloadも追加されています。 以前の`Joiner.awaitAll()`は削除されました。 timeoutの処理も`onTimeout()`から`timeout()`へ変わり、結果を返すか例外を送出します([JEP 533](https://openjdk.org/jeps/533))。
### subtaskの値とscope全体の結果は同じ型とは限らない
`StructuredTaskScope`の`T`は、起動したsubtaskが成功した場合の値の型です。 `R`はscope全体の`join()`が返す型なので、subtaskが`String`を返しても、scopeの結果は`List`にできます。 `R_X`は`join()`が失敗を知らせる際の例外型であり、成功結果の型へerrorを混在させるためのparameterではありません。 成功値の集約方法と失敗の伝え方を`Joiner`で決めると、scopeの型へ反映されます([StructuredTaskScope.Joinerのtype parameter](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/util/concurrent/StructuredTaskScope.Joiner.html))。
`allSuccessfulOrThrow()`なら、すべての成功値をlistへ集めます。 `awaitAllSuccessfulOrThrow()`は全成功を待ちますが、`join()`で取得するnon-nullな結果は作りません。 `anySuccessfulOrThrow()`では、成功したsubtaskの値を取得できればよく、すべての成功値を集める条件とは異なります。 3つのfactoryは、結果の容器に加えて、scopeの処理結果を確定する条件を変えます。 1個の失敗で全体を失敗にするpolicyと、少なくとも1個の成功で処理を進めるpolicyを選べます。
例外を変換するoverloadは、失敗を呼び出し元のAPIが公開する例外型へ合わせる用途を持ちます。 `Joiner.allSuccessfulOrThrow`へ、causeから`IOException`を作るfunctionを渡せます。 subtaskの`IllegalStateException`は、元の例外をcauseに持つ`IOException`へ変わります。 呼び出し元がcatchする型は、subtaskが送出した例外の型と同じである必要はありません。 新しい`R_X`によって、factoryで選んだ変換後の型を、`join()`の宣言へ持ち上げられます([JEP 533の例外型を指定するfactory](https://openjdk.org/jeps/533))。
### cancelが発生した後も、scopeを閉じるまで所有関係が残る
兄弟subtaskの一方が`CountDownLatch.await()`で停止している場合、もう一方の失敗によるcancelが、停止中のthreadへinterruptを要求します。 開始の順序もlatchで揃えると、待機中の兄弟が必ず存在する状態で失敗させられます。
```java
var entered = new CountDownLatch(1);
var interrupted = new AtomicBoolean();
try (var scope = StructuredTaskScope.open(
StructuredTaskScope.Joiner.allSuccessfulOrThrow(
cause -> new IOException("lookup", cause)))) {
scope.fork(() -> {
entered.countDown();
try {
new CountDownLatch(1).await();
return "unexpected";
} catch (InterruptedException error) {
interrupted.set(true);
throw error;
}
});
scope.fork(() -> {
entered.await();
throw new IllegalStateException("failed");
});
scope.join();
} catch (IOException error) {
System.out.println(error.getClass().getSimpleName()
+ "/" + error.getCause().getClass().getSimpleName());
}
System.out.println(interrupted.get());
```
```text
IOException/IllegalStateException
true
```
`join()`の失敗時点と、全subtaskの終了時点は、常に同じとは限りません。 cancelで結果を確定しても、interruptを受けたsubtaskの後処理は完了していない場合があります。 `close()`はsubtaskの終了を待ち、scopeの外へthreadを残さないようにします。 したがって、失敗直後に呼び出し元へ戻す構成とは、resourceの生存期間が異なります。 `try`の外側で例外をcatchする時点には、scopeの`close()`が完了し、起動した兄弟も終了しています([StructuredTaskScopeのclosing](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/util/concurrent/StructuredTaskScope.html))。
scopeにはowner threadがあります。 `fork`、`join`、`close`を任意の別threadから操作する、共有のtask managerとしてのAPIではありません。 親の処理がsubtaskを起動し、結果を待ち、同じ構造の範囲内で終了する関係をAPIが追跡します。 subtaskへ処理を渡すことと、scopeの管理権限をsubtaskへ移すことは異なります。 親が持つscopeの操作を無秩序に共有すると、APIが要求する所有関係に違反します([StructuredTaskScopeのownerと構造](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/util/concurrent/StructuredTaskScope.html))。
timeoutは`Configuration.withTimeout(Duration)`でscopeへ設定できます。 期限に到達するとscopeをcancelし、`Joiner.timeout()`がtimeout時の結果または例外を決めます。 期限を過ぎた処理へinterruptを要求しても、threadを強制的に破棄する機能ではないため、`close()`が待つ条件は残ります。 外部libraryがinterruptへ応答しない場合に、scopeのtimeoutだけを短くしても、scopeを閉じる総時間まで同じ長さへ制限されるとは限りません。 期限の到達を検出する条件と、起動した処理が終了する条件を、APIは別の段階で扱います([Joiner.timeout](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/util/concurrent/StructuredTaskScope.Joiner.html#timeout%28%29))。
## Vector API、PEM、JFRの変更
Vector APIは12回目のincubatorです。 CPUのSIMD命令に対応するvector演算をJavaから表現するAPIであり、利用時には`--add-modules jdk.incubator.vector`を指定します。 primitive patternや`LazyConstant`のpreviewを有効にする指定とは別に、moduleの追加が必要です([JEP 537](https://openjdk.org/jeps/537))。 JDK 27ではVector API自体の変更はありません。 ARMとRISC-Vのvector mathに使う同梱SLEEFは、3.6.1から3.9.0へ更新されています([JEP 537の更新対象](https://openjdk.org/jeps/537))。
### Vector APIではlane数と配列の末尾を分けて扱う
Vector APIのvectorは、同じ演算を適用する複数のlaneをまとめた値です。 `FloatVector`なら各laneが`float`を保持し、`mul(2)`は各laneへ2倍の演算を適用します。 `VectorSpecies`は、要素の型とvectorのshapeに対応する組み合わせを表します。 `SPECIES_PREFERRED`を使うコードでは、実行環境に適したlane数を取得します。 source内へ特定のlane数を固定せずにloopを組めます([VectorSpeciesのshapeとlane](https://docs.oracle.com/en/java/javase/27/docs/api/jdk.incubator.vector/jdk/incubator/vector/VectorSpecies.html))。
macOS/aarch64のTemurin 27+35では、preferred speciesのlane数は4です。 1から7までの7要素なら、先頭の4要素をvectorで処理し、残り3要素をscalarのloopで処理します。 vectorの反復上限には`species.loopBound(source.length)`を使います。 配列の長さをvector loopの上限に使うと、最後の反復で配列の外まで読み込む場合があるためです。
```java
var species = FloatVector.SPECIES_PREFERRED;
float[] source = {1, 2, 3, 4, 5, 6, 7};
float[] result = new float[source.length];
int i = 0;
for (; i < species.loopBound(source.length); i += species.length()) {
FloatVector.fromArray(species, source, i)
.mul(2).intoArray(result, i);
}
for (; i < source.length; i++) {
result[i] = source[i] * 2;
}
System.out.println(species.length()); // => 4
System.out.println(Arrays.toString(result)); // => [2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0]
```
末尾を処理する別の方法には、`indexInRange`で有効なlaneを示すmaskを作る方法があります。 maskは配列の範囲内にあるlaneだけを選ぶため、端数のある入力でも同じvectorのshapeを使う構成にできます。 ただし、すべての演算をmaskedな形へ変えれば必ず速くなるという関係ではありません。 targetの命令とJITが使える処理によって、scalarで末尾を扱う構成との性能差は変わります。 maskを使う構成では、`indexInRange`が選んだlaneだけをloadとstoreの対象へ指定します([VectorSpecies.indexInRange](https://docs.oracle.com/en/java/javase/27/docs/api/jdk.incubator.vector/jdk/incubator/vector/VectorSpecies.html#indexInRange%28int,int%29))。
JDK 27のVector APIは、12回目のincubatorでもAPI自体を変更していません。 SLEEFの更新は、vector mathの実装が利用するlibraryの更新です。 Javaのsourceに新しいmethodが増えたことと、同じmethodがtargetで使う実装を更新したことは、別の差分になります。 incubator moduleの解決には、previewの有効化とは別の指定が必要です。 classの`main`へ断片を置いた`Vector27.java`は、incubator moduleを指定して実行できます。
```sh
java --add-modules jdk.incubator.vector --source 27 Vector27.java
```
実行時のincubatorのwarningも、preview APIの有効化に関する診断とは異なるものです。
PEM encodingは3回目のpreviewとして、鍵や証明書などをPEM形式でencode、decodeするAPIを提供します。 `-----BEGIN ...-----`で囲まれたBase64表現を、Javaの暗号APIが扱うobjectと相互変換するための機能です。 文字列の区切りを手作業で切り出す処理と、暗号objectの変換を別々に実装する必要を減らします([JEP 538](https://openjdk.org/jeps/538))。
JDK 27では`PEM`がrecordから通常のclassへ変わり、Base64のbyte列を受け取るconstructorが加わりました。 `DEREncodable`は`BinaryEncodable`へ改名されています。 `PEMDecoder.withFactory`も`withFactoriesOf`へ変わり、providerから複数のfactoryを取得する動作を名前に表しています([JEP 538のAPI変更](https://openjdk.org/jeps/538))。
### PEMの文字列と暗号objectの境界をAPIで扱う
PEMには、`PUBLIC KEY`などのtypeを表すlabelと、Base64で表現した内容があります。 区切り行を除いてBase64をdecodeしただけでは、Javaの`PublicKey`や`X509Certificate`を作る処理は完了しません。 typeとbinary encodingに対応するfactoryを使い、暗号objectへ変換する段階が残ります。 `PEMDecoder`は、PEMのtypeに応じたobjectを返す処理まで含みます([PEMDecoderのtypeと返り値の対応](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/security/PEMDecoder.html))。
2048-bitのRSA key pairから公開鍵をencodeし、`PublicKey.class`を指定してdecodeできます。 鍵の値は生成するたびに変わりますが、encode前とdecode後のbinary encodingは一致します。
```java
var generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
var original = generator.generateKeyPair().getPublic();
var text = PEMEncoder.of().encodeToString(original);
var decoded = PEMDecoder.of().decode(text, PublicKey.class);
System.out.println(text.startsWith("-----BEGIN PUBLIC KEY-----")); // => true
System.out.println(Arrays.equals(
original.getEncoded(), decoded.getEncoded())); // => true
```
戻り値のclassを指定するoverloadは、castを省略するためだけのものではありません。 たとえばkey pairの内容から、`PrivateKey.class`を指定してprivate keyだけを取り出せます。 内容と指定したclassが合わない場合には`ClassCastException`になります。 名前のないBase64の塊を任意の暗号objectへ変換できるという契約ではなく、PEMの内容が要求した表現に対応する必要があります([PEMDecoder.decodeのclass指定](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/security/PEMDecoder.html#decode%28java.lang.String,java.lang.Class%29))。
対応する暗号objectがないtypeでは、decoderは`PEM`を返します。 `PEM.class`を明示して、type、Base64の内容、headerより前のleading dataへアクセスする使い方もできます。 `PEM`を直接構築するconstructorは、指定したtypeと内容が意味的に一致することまで検査しません。 たとえばlabelを`PUBLIC KEY`にした事実だけで、内容が有効な公開鍵だと検証した結果にはなりません。 PEMの構造を保持するobjectと、鍵を解釈して生成したobjectには、成立している条件の違いがあります([PEMのconstructorの検証範囲](https://docs.oracle.com/en/java/javase/27/docs/api/java.base/java/security/PEM.html))。
`withFactoriesOf(provider)`は、指定したproviderが提供するfactoryを使うdecoderを作ります。 PEMの構文を読み取る機能と、暗号algorithmごとに鍵や証明書を生成するproviderの選択が、API上で接続されます。 暗号化したprivate keyについては、decodeして`EncryptedPrivateKeyInfo`を得る経路と、復号を設定してprivate keyへ進む経路があります。 Base64が読めたことと、暗号化した鍵を復号できたことは同じ成功条件ではありません。 JDK 27では`CryptoException`も加わり、暗号処理に関わる失敗の表し方が整理されています([JEP 538のdecoderと例外](https://openjdk.org/jeps/538))。
JFRにはin-process data redactionが入り、記録がprocessの外へ出る前に機密情報を除く仕組みが加わりました。 対象にはcommand line引数、起動時の環境変数、system propertyなどが含まれます。 filterに一致した情報を、process外へ保存する前に除きます。 filterは`-XX:FlightRecorderOptions`の追加optionで指定できます([JEP 536](https://openjdk.org/jeps/536))。
### JFRへ書き出す前に値を置き換える
従来のJFR記録には、環境や起動引数を記録するeventが含まれます。 `jdk.InitialEnvironmentVariable`、`jdk.InitialSystemProperty`、`jdk.JVMInformation`などです。 起動時の環境や引数は障害調査の入力になりますが、起動設定に含まれたpasswordやtokenも同じ経路で記録され得ました。 JDK 27では、対象の引数や値をprocess内で`[REDACTED]`へ置き換えてから記録します。 保存したfileを後から編集する処理とは異なり、未加工の値を記録の一時fileやstreamへ出す前の段階で扱います([JEP 536の対象eventと処理の位置](https://openjdk.org/jeps/536#Description))。
`redact-key`は、環境変数とsystem propertyのkeyへ照合するfilterです。 keyが一致した場合には、key自体ではなく対応する値を置き換えます。 `redact-argument`はcommand line引数へ照合し、一致した引数を置き換えます。 照合は大文字と小文字を区別せず、`*`と`?`を使うglob patternです。 複数のfilterはsemicolonで区切るため、shellでcommandを記述する際には、option全体を1個の引数として渡す形になります([JEP 536のfilter構文](https://openjdk.org/jeps/536#Description))。
propertyを`report.secret=sample-value`と`report.label=public`にします。 `redact-key=*secret*`を適用すると、前者の値だけを置き換えます。
```sh
java -XX:StartFlightRecording=filename=redaction-code-review.jfr \
'-XX:FlightRecorderOptions=redact-key=*secret*' \
-Dreport.secret=sample-value -Dreport.label=public -version
jfr print --json --events jdk.InitialSystemProperty redaction-code-review.jfr \
| jaq -c '.recording.events[].values | select(.key == "report.secret" or .key == "report.label") | {key, value}'
```
JSONから対象のpropertyを取り出すと、置換した値と保持した値が並びます。
```jsonl
{"key":"report.secret","value":"[REDACTED]"}
{"key":"report.label","value":"public"}
```
`jdk.JVMInformation`が保持するJVMの引数でも、`-Dreport.secret=[REDACTED]`となっています。 propertyのeventだけを加工し、同じ値が残るcommand lineを無加工で保存する動作ではありません。
command lineで`--token sample-token`のように名前と値を分けている場合、`--token`だけを隠しても値が残ります。 `redact-argument=--token *`は、名前と次の1個の引数をまとめて照合するfilterです。 `javaArguments`では、名前と値の両方が`[REDACTED]`へ変わります。 filterにsecretの値を書かず、引数の形式から置き換える対象を選べます。 値が1個ではない形式なら、必要な引数の個数に合わせてpatternを指定します([JEP 536の複数引数の照合](https://openjdk.org/jeps/536#Matching-multiple-command-line-arguments))。
既定のkey filterには、password、secret、token、credentialなどに対応するpatternがあります。 独自のfilterを指定するとき、通常の指定は既定のfilter一覧を置き換えます。 既定値へ追加する場合には、先頭の`+`を使います。 独自filterだけを指定した起動では、既定のfilterを置き換えた旨のwarningを出します。 独自の1個のkeyを隠す設定でも、置換の構文では従来のpasswordやtokenを隠すpatternが外れます。 追加と置換を区別するのは、既定の保護を維持する条件です([JEP 536の既定filter](https://openjdk.org/jeps/536#Default-filters))。
filterをfileから読む場合には、`redact-key=@keys.txt`のように`@`を付けます。 fileはJVMの起動時に読み込み、1行に1個のfilterを記述します。 読み込めないfileを指定した場合は、file名を含むerrorを表示してJVMが終了します。 長いfilter一覧を複数の起動commandへ複製する代わりに、同じfileを配置して利用する経路です。 `-Xlog:jfr+redact=debug`は、filterに一致した引数やkeyをdebug logへ出力します([JEP 536のfilter file](https://openjdk.org/jeps/536#Loading-filters-from-a-file))。
redactionが対象にするのは、実装が指定するeventのfieldとfilterの条件です。 アプリケーションが独自eventのmessageへ書いた任意の内容を、すべて自動判定して除く機能ではありません。 既定で置き換えたfieldが増えるため、従来のJFR記録を解析する処理には、実値の代わりに`[REDACTED]`が入る差も生じます。 記録の構造が壊れた結果ではなく、対象の値を出さない処理の結果としてplaceholderが入ります。 機密値を除く条件を増やしつつ、調査に必要な非機密の起動設定を残すことが、event全体を無効にする構成との動作の差です。
filterの構文をglobにした理由には、JVM内部で照合する処理の制約もあります。 JEP 536では、C++の正規表現libraryが例外を送出し得る一方、HotSpot内部ではC++の例外を使わない規約を挙げています。 Javaの正規表現APIを呼ぶ案では、起動時間への影響や、JVMからJavaへ呼び戻せない位置への拡張が問題になります。 JEP 536では、引数やkeyの名前を選ぶpatternを、globとしてprocess内で扱う方式を採用しています。 filterの構文には、正規表現の量指定子やgroupを含みません([JEP 536で比較された照合方式](https://openjdk.org/jeps/536#Alternatives))。
### Rust 1.99の変更をコードで追う
URL: https://www.sakashita.page/rust-1-99-release-notes-and-analysis/
Last updated: 2026-10-25T04:59:59.000Z
Rust 1.99.0は2026年10月1日、修正版の1.99.1は10月15日に公開されました。 1.99.0ではC variadic functionの定義がstableになり、`Vec`や`Box`の所有権を`NonNull`で受け渡すAPIも加わっています。 1.99.1では、Cとの境界に関わる`bool`のABIなど、3件のregressionが修正されています([1.99.0の公式発表](https://blog.rust-lang.org/2026/10/01/Rust-1.99.0/)、[1.99.1の公式発表](https://blog.rust-lang.org/2026/10/15/Rust-1.99.1/))。
## Cの可変長引数をRust側で受け取る
従来のstable Rustでも、C variadic functionを宣言して呼び出すことはできました。 1.99では`extern "C"`と`extern "C-unwind"`のfunctionをRust側で定義できるため、Cから呼ばれる可変長引数の処理もRustで実装できます。 引数の`...`に対応する`VaList`から、`next_arg::()`で値を順に取り出します([VaListのAPI](https://doc.rust-lang.org/std/ffi/struct.VaList.html))。
```rust
unsafe extern "C" fn sum(count: u32, mut values: ...) -> i32 {
let mut result = 0;
for _ in 0..count {
result += unsafe { values.next_arg::() };
}
result
}
fn main() {
println!("sum={}", unsafe { sum(3, 4_i32, 5_i32, 6_i32) }); // => sum=15
}
```
コード例の対象はRust 1.99.1、darwin/aarch64です。 `count`は残りの引数の個数を示す独自の約束であり、`VaList`が残数や型を検査しているわけではありません。 呼び出し側が2個しか渡していないのに3個読み出したり、実際には整数の引数を`f64`として取り出したりすると、`next_arg`の安全性の条件に違反します。
Cのdefault argument promotionにも従う必要があります。 たとえば`float`は`double`に、小さい整数型は整数昇格後の型になります。 したがって、C側で`float`だった値をRust側で`next_arg::()`として受け取る実装にはできません。 引数を渡した後のABI上の型に合わせて読み出します([VaList::next\_argのSafety](https://doc.rust-lang.org/std/ffi/struct.VaList.html#method.next%5Farg))。
`VaList`の初期化と終了処理はcompilerが用意し、複製には`Clone`を使えます。 複製した値は`va_copy`に対応する独立した走査位置を持ち、破棄時には`va_end`に対応する処理が行われます。 元の走査位置を保持したまま引数を2回走査する場合も、内部表現をコピーせず`clone()`を使います([VaListの複製と破棄](https://doc.rust-lang.org/std/ffi/struct.VaList.html#impl-Clone-for-VaList%3C'f%3E))。
### 呼び出しの個数と型を、functionの型だけでは表せない
固定の引数だけを持つfunctionなら、引数の個数や型が宣言に現れます。 可変部分を`...`で受け取るfunctionでは、可変部分の型を宣言に列挙できません。 `sum`の第1引数が3であることと、後ろに3個の整数があることは、呼び出し側と実装側で共有する約束です。 compilerが3という値から後続の引数を検査する仕組みではありません。 したがって、Rustで定義できるようになった後も、Cの呼び出し規約に依存する境界は残ります([C variadic functionの規則](https://doc.rust-lang.org/reference/items/functions.html#variadic-functions))。
同じ理由で、`next_arg`のtype argumentは、実装が次に読み出す値の解釈を決めます。 実引数を調べて適切な型を選んでくれる操作ではありません。 たとえば整数、浮動小数点数、pointerを受け取る独自の形式なら、実装も指定された順序を維持します。 順番を間違えた後で残りの引数の解釈を正しく戻せる保証はなく、不一致を通常の`Result`として回収するAPIでもありません。 可変長引数を受けるfunction自身で、読み出す型の集合と順番を決めます([VaList::next\_arg](https://doc.rust-lang.org/std/ffi/struct.VaList.html#method.next%5Farg))。
`sum`を呼ぶ実引数と、`next_arg`で取り出す型には、同じ`i32`を指定しています。 単に`4`とだけ書いた値のtype inferenceへ依存せず、呼び出し規約で受け渡す型をコードに残すためです。 加算結果の15は、4、5、6を順に取得できた場合の結果です。 別の型や個数を受け取る呼び出しには、受け取る形式に対応した読み出し処理が必要です。 functionをCへ公開する場合も、利用者が渡す形式を別に定める必要があります。
`extern "C-unwind"`を選ぶ条件と、可変長引数を扱う条件は別です。 ABIを越えるunwindを許す境界と、許さない境界を区別する指定です。 `VaList`の型検査を追加する指定ではありません。 可変部分の読み出しに失敗する呼び出しを、unwind可能なABIへ変えるだけで正しく扱えるようにはなりません。 可変長引数の契約と、例外やpanicが境界を越える契約を、呼び出し規約に合わせます([RustonomiconのFFIとunwind](https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-unwinding))。
### `VaList`を複製した位置から読み直す
走査位置の複製は、最初から引数を取り直す操作とは異なります。 最初の整数4を取得してから`VaList`を複製すると、元の値と複製した値の両方が、次の整数5の手前を指します。 元の位置は次の整数5へ進んでいるので、複製した位置も5の手前になります。
```rust
unsafe extern "C" fn cursors(_count: u32, mut values: ...) -> i32 {
let first = unsafe { values.next_arg::() };
let mut copied = values.clone();
let second = unsafe { values.next_arg::() };
let second_copy = unsafe { copied.next_arg::() };
println!("cursors={first}/{second}/{second_copy}"); // => cursors=4/5/5
first + second + second_copy
}
fn main() {
let total = unsafe { cursors(3, 4_i32, 5_i32, 6_i32) };
assert_eq!(total, 14);
}
```
5を2回読んでいるのは、同じ走査位置を2つの独立した状態から読んだからです。 元の`values`から5を読んでも、複製した`copied`は5の手前に残ります。 可変長引数を数える処理と実際に処理する操作を分ける場合にも、必要なのは走査位置の複製であり、引数のbyte列を手作業で複製することではありません。
`cursors`のbodyは`_count`を参照しません。 `cursors`のbodyは先頭2個の整数だけを読み、残数を計算しません。 `sum`のように個数に従ってすべて読むAPIへ変える場合は、複製した位置から残り何個を読むかも実装で管理します。 複製した後に最初の個数を繰り返し回数に使うと、すでに消費した分を余計に読み出す場合があります。 走査位置の保存と、残数の管理は別の状態です。
新しい定義機能は、Rustで型の決まったsliceを受け取るfunctionを、可変長引数へ自動変換するものでもありません。 sliceには要素の型と長さが付随しますが、C variadic functionでは可変部分の型を共有する別の形式が必要です。 Rust同士の呼び出しだけで完結するAPIなら、sliceで表現できる情報を可変部分へ移す必然性はありません。 1.99の追加が直接埋めるのは、C側が要求する可変長引数のentry pointをRust側で定義する部分です([RustのfunctionとABI](https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier))。
## `NonNull`で所有権を外へ移し、同じallocationを取り戻す
`Vec::into_parts`は`Vec`を消費し、`NonNull`、長さ、capacityを返します。 返された値を`Vec::from_parts`に渡せば、同じallocationを所有する`Vec`を再構築できます。 要素を再コピーせず、allocationの管理を移します([Vec::into\_parts](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html#method.into%5Fparts))。 以下の断片は`main`のbodyへ置きます。
```rust
let values = vec![String::from("red"), String::from("blue")];
let (pointer, length, capacity) = values.into_parts();
let restored = unsafe { Vec::from_parts(pointer, length, capacity) };
println!("owned={restored:?}"); // => owned=["red", "blue"]
```
`into_parts`の後は元の`Vec`が存在しないため、`restored`の破棄で要素とallocationが解放されます。 `from_parts`を同じ値で2回呼び出すと、allocationを解放する所有者が重複します。 また、capacityや要素型を変えると、allocationと解放時のlayoutが一致しなくなり得ます。 `NonNull`が保証するのはnullではないことまでであり、所有権、初期化済みの要素数、allocationのlayoutは呼び出し側が維持します([Vec::from\_partsのSafety](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html#method.from%5Fparts))。

Vecから取り出したpointer、length、capacityを再構築したVecへ移す図
`Box`にも`into_non_null`と`from_non_null`が加わりました。 `Box::into_non_null(boxed)`は`Box`を消費して`NonNull`を返し、`Box::from_non_null(pointer)`は所有者を再構築します。 後で解放する値の所有権を渡す場合、`Box::leak`を経由せず往復できます([into\_non\_null](https://doc.rust-lang.org/std/boxed/struct.Box.html#method.into%5Fnon%5Fnull)、[from\_non\_null](https://doc.rust-lang.org/std/boxed/struct.Box.html#method.from%5Fnon%5Fnull))。
```rust
let boxed = Box::new(String::from("owned"));
let pointer = Box::into_non_null(boxed);
let restored = unsafe { Box::from_non_null(pointer) };
println!("box={restored}"); // => box=owned
```
`Box`のallocationを保持したまま所有者だけを外し、最後に`Box`へ戻しているため、値を意図的に残し続ける処理は含まれていません。
### `NonNull`へ変えても、所有者の個数は別に管理する
`Vec::into_parts`の返り値にあるpointerは、要素の先頭を示します。 長さは初期化済みの要素数、capacityはallocationが確保している要素数です。 再構築する側では、破棄する要素数と、allocationの解放に使うlayoutを両方復元します。 長さだけを保存してcapacityを失うと、要素の境界は分かっても、元のallocationを正しい条件で解放できなくなります([Vecの保証](https://doc.rust-lang.org/std/vec/struct.Vec.html#guarantees))。
`Vec`の場合は、allocationの区別がさらに必要です。 `Vec`は`String`を並べる領域を確保し、各`String`は文字列用のallocationを別に持ちます。 `into_parts`で外へ移すのは、並んだ`String`の所有権も含む`Vec`全体の管理です。 再構築した`Vec`を破棄すると、初期化済みの`String`のdestructorが動き、各文字列のallocationも解放されます。 要素用の領域だけを解放して済む操作にはできません([Rustonomiconのdestructor処理](https://doc.rust-lang.org/nomicon/vec/vec-dealloc.html))。
破棄時にcounterを増やす要素を使うと、所有権の移動と要素の破棄を分けて表せます。
```rust
use std::cell::RefCell;
use std::rc::Rc;
struct Tracked(Rc>);
impl Drop for Tracked {
fn drop(&mut self) {
*self.0.borrow_mut() += 1;
}
}
let drops = Rc::new(RefCell::new(0));
let values = vec![Tracked(drops.clone()), Tracked(drops.clone())];
let address = values.as_ptr();
let (pointer, length, capacity) = values.into_parts();
assert_eq!(*drops.borrow(), 0);
let restored = unsafe { Vec::from_parts(pointer, length, capacity) };
assert_eq!(address, restored.as_ptr());
drop(restored);
assert_eq!(*drops.borrow(), 2);
```
`into_parts`は要素を破棄せず、`from_parts`は元のallocationを再利用します。 counterが増えるのは、所有権を受け取った`restored`を破棄する段階です。
`NonNull`のpointer値を複製しても、要素とallocationの所有権は複製されません。 複製したpointerを使って2つの`Vec`を再構築してしまうと、要素とallocationを破棄する権限まで重複させます。 最初の`Vec`を破棄すると、残った`Vec`はすでに解放した領域を保持します。 新APIがnullの扱いを型へ移しても、再構築を1回に限る契約は`from_parts`を呼ぶ側に残ります([NonNullの契約](https://doc.rust-lang.org/std/ptr/struct.NonNull.html))。
`Box::into_non_null`でも、pointerの値を保存することと、解放を担当する所有者を保存することを区別します。 `Box`を消費した直後には、通常のscope終了で値を解放する`Box`は残りません。 再構築しない経路を選べば、値を解放する処理も自動では復活しません。 たとえば外部のcallbackへpointerを渡す設計では、成功時だけでなく、callbackを登録できなかった経路でも所有権を回収する必要があります。 登録先に所有権を渡せた経路と、呼び出し元へ戻す経路を同時に成立させない設計にします([Box::from\_non\_nullのSafety](https://doc.rust-lang.org/std/boxed/struct.Box.html#method.from%5Fnon%5Fnull))。
従来の`ManuallyDrop`を使った実装では、自動のdestructorを止めてからpointer、長さ、capacityを取り出す方法がありました。 `ManuallyDrop`は内側の値のlayoutやbitの有効性を変えず、破棄を抑制する型です。 したがって、破棄済みの値を後から安全な参照として公開してよい状態へ変える型ではありません。 `into_parts`は`Vec`を消費する呼び出しに管理の移動をまとめるため、破棄を止めた`Vec`を別に持ち続ける必要を減らします([ManuallyDropのAPI](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html))。
`Box::leak`から参照を得る使い方も、所有権を移す使い方とは状態が異なります。 `leak`は`Box`が持っていた値を自動解放しない状態へ移し、参照を返します。 後から参照をraw pointerへ変換して所有者を復元する設計では、まだ利用できる参照を残していないかまで考える必要があります。 最初から外部へ所有権を移して戻す用途なら、参照を作る過程を挟まず、消費するAPIで管理の移動を示せます([Box::leak](https://doc.rust-lang.org/std/boxed/struct.Box.html#method.leak))。
### 空の`Vec`とzero-sized typeはallocationの例外になる
空の`Vec`では、長さとcapacityは両方0です。 ただし、返されるpointerの型は`NonNull`です。 要素を1個も確保していない状態でも、null pointerを返すAPIにはなっていません。 nullではないpointerを、実際のallocationや初期化済みの値が存在する証拠として扱うことはできません([Rustonomiconの空のVec](https://doc.rust-lang.org/nomicon/vec/vec-alloc.html))。
空のcollectionを外部へ渡す際に、長さ0ならpointerを使わない約束を設けるAPIはあります。 しかし、外部APIの空の状態をnullで表す契約なら、`NonNull`の値を渡すだけで同じ表現になるわけではありません。 Rust側の空collectionの表現と、外部APIが受け取る空の表現を接続する処理が必要です。 反対方向から受け取る場合も、nullを`from_parts`へ無条件で渡す操作にはできません。 受け渡す側の型が保証する状態を、相手側の表現へ変換します。
zero-sized typeでは、要素数と確保byte数の対応も変わります。 `vec![(); 3]`では、長さは3、capacityは`usize::MAX`になります。 `()`を3個保持していても、要素用に3 byte以上のallocationを確保したことにはなりません。 sizeが0の型は、要素数を増やしても要素用のbyte数が増えないためです([Rustonomiconのzero-sized type](https://doc.rust-lang.org/nomicon/vec/vec-zsts.html))。
zero-sized typeを扱う実装では、pointerの数値を要素sizeだけ増やして反復位置を表す方法を流用できません。 要素sizeを掛けた移動量は0なので、異なる論理的な要素の間でもbyte addressは変わりません。 一方、要素数の上限やdestructorを呼ぶ回数は依然として管理する必要があります。 genericな所有権移動に新APIを使う場合も、要素数、capacity、allocationのbyte数を別に管理します。 zero-sized typeでは、要素数からallocationのbyte数を逆算できないためです([zero-sized typeの反復とcapacity](https://doc.rust-lang.org/nomicon/vec/vec-zsts.html))。
## 参照を作らずに動的なlayoutを取得する
`Layout::for_value_raw`、`mem::size_of_val_raw`、`mem::align_of_val_raw`もstableになりました。 いずれもraw pointerから、指している値のsizeやalignmentを求めます。 sliceなら長さ、trait objectならvtableなど、unsized typeに付随するmetadataを使って動的なlayoutを決めます([Layout::for\_value\_raw](https://doc.rust-lang.org/stable/core/alloc/struct.Layout.html#method.for%5Fvalue%5Fraw))。
```rust
use std::alloc::Layout;
let data = [1_u16, 2, 3];
let pointer: *const [u16] = &data[..];
let layout = unsafe { Layout::for_value_raw(pointer) };
println!("layout={}/{}", layout.size(), layout.align()); // => layout=6/2
```
`u16`が3個なのでsizeは6 byteであり、alignmentは2 byteになります。 `&data[..]`は有効なsliceを作りますが、raw版のAPIが任意のmetadataを受け付けるわけではありません。 たとえばsliceの長さが初期化されていることや、全体のsizeが`isize`の範囲に収まることなど、API固有の条件を満たす必要があります([for\_value\_rawのSafety](https://doc.rust-lang.org/stable/core/alloc/struct.Layout.html#method.for%5Fvalue%5Fraw))。
### raw pointerからの照会が必要になる位置
`size_of::()`は、型だけでsizeが決まる場合に使えます。 `[u16]`のようなsliceでは長さが型名に含まれないため、具体的な値に付随するmetadataからsizeを求めます。 `size_of_val`は参照を受け取り、`size_of_val_raw`はraw pointerを受け取ります。 新APIは、raw pointerを扱う処理の途中で、照会のためだけに参照を作る必要を減らします([size\_of\_val\_raw](https://doc.rust-lang.org/std/mem/fn.size%5Fof%5Fval%5Fraw.html))。
たとえばallocationの管理を外へ移した後でも、unsizedな値のlayoutを保持しておく必要はあります。 再構築や解放で使うsizeとalignmentは、値の中身を読み取る処理とは別の情報です。 raw版のAPIはlayoutの照会を表現しますが、pointerが値を読み出せる状態にあると追加で保証するわけではありません。 照会できたlayoutから、参照を作れることや、参照を使って値を変更できることまで導くことはできません([align\_of\_val\_raw](https://doc.rust-lang.org/std/mem/fn.align%5Fof%5Fval%5Fraw.html))。
trait objectでは、長さの代わりにvtableから具体的な型のlayoutを求めます。 `[u8; 12]`を`&dyn Debug`として扱う場合も、raw pointerに付随するvtableからlayoutを求めます。 `dyn Debug`という名前だけから12という値は決まらず、元の具体的な型に対応するmetadataが結果を決めています。
```rust
let value = [7_u8; 12];
let erased: &dyn std::fmt::Debug = &value;
let pointer = erased as *const dyn std::fmt::Debug;
let layout = unsafe { Layout::for_value_raw(pointer) };
println!("erased-layout={}/{}", layout.size(), layout.align()); // => erased-layout=12/1
```
`erased as *const dyn Debug`は、有効なtrait objectの参照をraw pointerへ変換します。 vtableを任意の整数値から組み立てる処理や、別の具体型のmetadataへ付け替える処理は含みません。 layout照会のためのmetadataが正しい経路で得られていることを、呼び出しの前提にしています。 raw pointerを受け付けるというAPIの形だけを根拠に、任意のbit列を入力できるとは解釈できません([Layout::for\_value\_rawの契約](https://doc.rust-lang.org/std/alloc/struct.Layout.html#method.for%5Fvalue%5Fraw))。
sliceの例でsizeが6、alignmentが2だったのも、同じ分離を示します。 長さ3は要素数であり、sizeの6はbyte数です。 alignmentの2は、先頭addressに要求する境界です。 要素数、全体のbyte数、addressの条件は互いに別の数量なので、allocation APIへ渡す場合も、正しい役割の値を使います。
`Layout`のsizeが求められても、allocatorから同じsizeだけ確保し直せば、既存の値を移動してよいとは限りません。 値の所有権、初期化状態、pinningの契約はlayoutとは独立して残ります。 1.99で追加された所有権移動のAPIとlayout照会のAPIを組み合わせる場合も、分解時の管理情報と、型やmetadataから得るlayout情報を混同しない構成にします。 前者は解放を担当する所有者の状態を表し、後者は確保領域に必要な形を表します。
## collectionと文字列の小さな追加
`VecDeque::retain_back(len)`は、末尾から`len`個を残して先頭側を削除します。 既存の`truncate(len)`が先頭側を残すのに対し、直近の履歴を残す処理を1回の呼び出しで表現できます。 `len`が現在の長さ以上なら変更しません([VecDeque::retain\_back](https://doc.rust-lang.org/stable/std/collections/struct.VecDeque.html#method.retain%5Fback))。
```rust
use std::collections::VecDeque;
let mut queue = VecDeque::from([10, 20, 30, 40]);
queue.retain_back(2);
println!("tail={queue:?}");
```
出力は`tail=[30, 40]`です。 predicateを渡す既存の`retain`とは異なり、`retain_back`に渡す値は残す個数です。
### 末尾の履歴を残す処理と、要素ごとの選別を分ける
`truncate`は先頭側を残し、`retain_back`は末尾側を残します。
```rust
let mut head = VecDeque::from([10, 20, 30, 40]);
let mut tail = head.clone();
head.truncate(2);
tail.retain_back(2);
assert_eq!(head, VecDeque::from([10, 20]));
assert_eq!(tail, VecDeque::from([30, 40]));
```
同じ長さ2への短縮でも、保存したい履歴が古い側か新しい側かで呼び出す操作が変わります。 `VecDeque`を新しい要素を末尾へ追加する履歴として使う場合、`retain_back`は直近の要素を残す操作に直接対応します。 残す個数を調整するためだけにpredicateへ外部のcounterを持たせる必要がなくなります。
`retain`は、各要素について残すかを判定する操作です。 たとえば値が条件を満たす要素だけを残す処理と、直近2件を残す処理では、削除する条件が違います。 新APIの`len`は条件に合う要素数の上限ではなく、末尾から保存する個数です。 `retain_back(0)`なら全要素を削除し、現在の長さ以上を指定した場合はすべての要素を残します([retain\_backの境界条件](https://doc.rust-lang.org/std/collections/struct.VecDeque.html#method.retain%5Fback))。
末尾を残した後は、論理的なindexも残った要素に対して振り直されます。 `tail`の操作前のindex 2にある30は、`retain_back(2)`の後でindex 0になります。 長さ2へ短縮する処理は、元のindexを保ったまま前半を空欄にする処理ではありません。 外部に元のindexを保存するAPIへ組み込む場合、保存したindexが短縮後にも同じ値を指すとは扱えません。
### `Box`に入った配列を移動しながら反復する
`IntoIterator`の追加は、配列を持つ`Box`を所有した反復と、参照を借りた反復の両方に関係します。 `&mut Box<[String; 2]>`の反復は各文字列を変更でき、所有した`Box`の反復は文字列自体を移動できます([Box<\[T; N\]>のIntoIterator](https://doc.rust-lang.org/std/boxed/struct.Box.html#impl-IntoIterator-for-Box%3C%5BT;+N%5D,+A%3E))。
```rust
let mut array = Box::new([String::from("a"), String::from("b")]);
for value in &mut array {
value.push('!');
}
let moved: Vec = array.into_iter().collect();
assert_eq!(moved, ["a!", "b!"]);
```
借用した反復なら、元の`Box`を後から使えます。 所有した反復なら、元の`Box`を消費し、要素の所有権をiteratorへ移します。 要素が`Copy`でない`String`でも、所有権を持つ経路なので値をcloneする必要はありません。 配列をheap上に置いたまま受け渡すAPIで、配列の要素を最後に消費する処理まで同じ型からつなげられます。
反復の途中でiteratorを破棄した場合は、取り出していない要素をiterator側が管理します。 元の`Box`を消費した後にも、呼び出し元が元の`Box`を別途解放する処理を持つ構成にはしません。 `Vec::into_parts`とは異なり、利用者がrawな管理情報を受け取るAPIではないためです。 同じ所有権の移動でも、safeなiteratorとして要素を消費する場合と、raw pointerで管理を引き継ぐ場合とで、呼び出し側に残る責任が変わります([IntoIteratorの契約](https://doc.rust-lang.org/std/iter/trait.IntoIterator.html))。
文字列では、`Vec`を消費する`String::from_utf8_lossy_owned`と、`FromUtf8Error::into_utf8_lossy`が加わりました。 不正なUTF-8の部分は、replacement characterへ置き換えます。
```rust
assert_eq!(String::from_utf8_lossy_owned(vec![65, 255, 66]), "A�B");
```
所有しているbyte列を受け取るため、有効なUTF-8なら元のallocationを利用できます([String::from\_utf8\_lossy\_owned](https://doc.rust-lang.org/stable/std/string/struct.String.html#method.from%5Futf8%5Flossy%5Fowned))。
### 有効な入力のallocationを引き継ぎ、不正な入力では内容を変換する
借用した`&[u8]`を受け取るlossy変換の結果は、入力の借用か、新しい文字列になります。 所有した`Vec`を受け取る新APIでは、入力のallocationを所有したまま変換を進められます。 有効なUTF-8では、入力と結果の先頭pointerが一致します。
```rust
let bytes = b"valid".to_vec();
let address = bytes.as_ptr();
let text = String::from_utf8_lossy_owned(bytes);
assert_eq!(address, text.as_ptr());
```
不正なUTF-8を含む入力では、結果の文字列のbyte数が元の長さと一致しない場合があります。 `[65, 255, 66]`は3 byteですが、置換後の`A�B`はUTF-8で5 byteです。 `A`と`B`が1 byteずつ、replacement characterが3 byteになるためです。 所有したbufferを受け取るAPIでも、元のcapacityで必ず変換を完了できるとは限りません。
lossy変換では、不正なbyteを文字として保持するのではなく、置換した文字列を作ります。 したがって、結果の`String`を再びbyte列へ戻しても、元の255というbyteは復元できません。 `from_utf8`が返したエラーを記録して元のbyte列を保ちたい経路と、文字列として処理を続ける経路では、保持する情報が変わります。 新APIは後者を所有した入力から直接行うための追加です([FromUtf8Error::into\_utf8\_lossy](https://doc.rust-lang.org/std/string/struct.FromUtf8Error.html#method.into%5Futf8%5Flossy))。
`String::from_utf8`は最初の不正なbyteの位置を返し、errorからlossy変換へ進めます。
```rust
let error = String::from_utf8(vec![65, 255, 66]).unwrap_err();
assert_eq!(error.utf8_error().valid_up_to(), 1);
assert_eq!(error.into_utf8_lossy(), "A�B");
```
エラーからbyte列を取り出し、借用のlossy変換を呼び、さらに所有した文字列へ変換する操作を、エラーを消費する呼び出しへまとめられます。
正常な入力だけを受け付ける契約なら、`from_utf8`の成功と失敗を区別する意味は残ります。 lossy版へ置き換えると、以前は失敗していた入力を、内容の変わった文字列として受け付ける動作になります。 既存の呼び出しを新しい所有版へ移す場合にも、allocationの扱いだけでなく、不正な入力を拒否するのか置換するのかを維持します。 文字列としての妥当性を回復することと、入力の情報を完全に保つことは異なる結果です。
ほかにも、`Box<[T; N]>`と参照に対する`IntoIterator`、`StepBy`の`FusedIterator`実装が追加されています。 時刻設定には`fs::set_times`と`fs::set_times_nofollow`が加わりました。 後者はsymbolic linkの参照先をたどらず、link自体の時刻を変更します([1.99.0のAPI追加](https://doc.rust-lang.org/stable/releases.html#version-1990-2026-10-01))。
## Cargoの既定動作と互換性への影響
Cargoには`debug` profileが追加され、現時点では`dev`と同じ設定になります。 CI環境ではincremental compilationが既定で無効になりました。 edition 2024では、継承した`default-features = true`を上書きできます。 member側で`default-features = false`を指定します。 edition 2024で従来は拒否されていた組み合わせを受け付けるため、workspace側の既定値を残したままmemberだけで無効化できます([Cargoを含む1.99.0の変更履歴](https://doc.rust-lang.org/stable/releases.html#cargo))。
### workspaceからの継承と、dependency graph全体のfeature解決
`default-features`の継承では、同じdependencyを指すmanifestの設定を採用する優先順位が変わりました。 edition 2024以降では、member側に値があればmember側を使い、なければworkspace側の値を使います。 両方で省略した場合は、通常のdependencyと同じく有効になります。 したがって、workspace側が`true`でも、member側の`false`によって当該dependency経路のdefault featureを外せます。 個別のmemberだけが小さい機能集合を必要とする場合に、workspace全体の既定値を変更せず指定できます([RFC 3945の設定の優先順位](https://github.com/rust-lang/rfcs/blob/master/text/3945-inherit-default-features.md#reference-level-explanation))。
`dep`の`rich`をdefault featureへ含めたworkspaceを考えます。
```toml
[features]
default = ["rich"]
rich = []
```
`off`はworkspaceから継承しながらdefaultを無効にし、`on`は継承した既定値を使います。 `dep::rich_enabled()`は`cfg!(feature = "rich")`を返し、build時に選択したfeatureをbooleanへ変換します。
```sh
cargo run -p off
# => rich=false
cargo run -p on
# => rich=true
```
同じdependencyのsourceを継承した2個のmemberでも、個別に選択したbuildでは異なるfeature集合になります。
```toml
[workspace]
members = ["dep", "off", "on"]
resolver = "3"
[workspace.dependencies]
dep = { path = "dep", default-features = true }
```
`off`のmanifestでは、`[dependencies]`へ`dep = { workspace = true, default-features = false }`を記述しています。 ただし、`false`はdependency graph全体からfeatureを禁止する指定ではありません。 `off`と`on`を一緒にbuildすると、`off`側でも`rich`が有効になります。
```sh
cargo build --workspace --target-dir target
./target/debug/off
# => rich=true
```
`on`の経路が同じ`dep`のdefault featureを要求し、feature unificationで要求を合わせたためです。 manifestの継承で採用する値と、複数のdependency経路から集まったfeature要求は、異なる段階で処理されます([Cargoのfeature unification](https://doc.rust-lang.org/cargo/reference/features.html#feature-unification))。
`default-features = false`を追加しても、別のmemberやdependencyから同じfeatureへの要求があれば有効になります。 たとえば`no_std`向けmemberが単体ではbuildできても、別のmemberを同時に選択した結果、`std`を使うfeatureが加わる構成はあり得ます。 新しい継承規則は、memberがdependencyを宣言する自由を増やしますが、featureを足し合わせるCargoの規則を、featureの減算へ変更してはいません。 不要な機能を確実に含めないbuildでは、実行するcommandが選択するpackage集合まで入力の一部になります。
resolverには、build-dependencyと通常のdependency、対象外のplatformのdependencyなどで、featureを不用意に共有しない規則もあります。 dependencyを分離する条件では、同じversionのcrateが異なるfeature集合で複数回buildされることもあります。 したがって、workspace内に同じcrate名が現れるという理由だけで、すべてのbuild単位のfeatureが同じになるとも限りません。 `off`と`on`からの通常のdependencyを同じtargetでbuildすると、`dep`へのfeature要求が合流します([resolver version 2以降の分離条件](https://doc.rust-lang.org/cargo/reference/features.html#feature-resolver-version-2))。
### `debug` profileという名前と、debug情報の設定
`cargo build --profile debug`は1.99で組み込みprofileとして受け付けられます。 Cargoのbuild完了表示にも、選択したprofile名が現れます。
```sh
cargo build --profile debug
# => Finished `debug` profile [unoptimized + debuginfo] ...
```
1.99時点の`debug`と`dev`の既定設定は同じです。 新しい名前を選んだだけで、実行時の最適化やdebug情報の量が従来の`dev`から変わるわけではありません。 Cargo側は、将来`dev`を短い開発反復向けに調整する準備として、debugging向けの名前を分けています([debug profileを追加したPR #17214](https://github.com/rust-lang/cargo/pull/17214))。
profile名の`debug`と、profile内の`debug`という設定は別の対象です。 後者は`rustc`へ渡すdebug情報の量を制御します。 たとえば`line-tables-only`はbacktraceへfile名や行番号を残す用途を持ちますが、変数やparameterの情報までは含めません。 最適化の程度は`opt-level`であり、整数overflowの実行時検査は`overflow-checks`です。 profile名を変える操作だけでは、各設定を個別に変更した結果を代替しません([Cargoのdebug情報と最適化設定](https://doc.rust-lang.org/cargo/reference/profiles.html#debug))。
workspaceのprofile設定は、rootの`Cargo.toml`から読み込まれます。 dependencyのmanifestへprofileを書いても、利用側workspaceのbuild設定を強制できません。 たとえばlibrary側でdebug情報を省略する設定を持っていても、利用側のrootで選ぶ設定がbuildに適用されます。 新しい`debug` profileも、memberごとに分散せず、rootのmanifestへ設定します([profile設定を読むmanifestの範囲](https://doc.rust-lang.org/cargo/reference/profiles.html))。
### CIではincremental compilationの保存先を使う前提が変わる
CIの既定値を変えた理由は、incremental compilationの状態を保存する処理にも時間と容量が必要だからです。 次回のbuildで状態を再利用しないrunnerでは、状態のserializationとdiskへの書き込みが、再利用による短縮へつながりません。 再利用するrunnerでも、増えたcacheを圧縮し、networkで転送する処理が追加されます。 Cargoの変更は、CIでの再利用率が低い場合にまで、incremental compilationを既定で有効にする構成をやめたものです([CIの既定値を変更したPR #17220](https://github.com/rust-lang/cargo/pull/17220))。
CIの判定には`CI`環境変数を使います。 設定の優先順位では、`CARGO_INCREMENTAL`と`build.incremental`がCIの判定より上にあります。 したがって、継続して同じbuild状態を使う専用runnerなら、明示した設定で有効にする経路は残ります。 逆に、profileの既定値だけへ依存していたCIでは、1.99への更新によってincremental用の状態を生成しなくなる場合があります。 変更の対象はcompilerの中間状態であり、`Cargo.lock`を無視したり、dependencyのversion解決を変更したりする機能ではありません。
同じCIであっても、incrementalの保存量が減ったことと、build全体が速くなったことは同じ測定ではありません。 Cargoが変更したのは、再利用の見込みが不明な環境で選ぶ既定値です。 既存のCI設定で`CARGO_INCREMENTAL=0`を指定していた場合は、既定値の変更前から無効だったので挙動は変わりません。
compiler側では、generic itemへの`no_mangle`がhard errorになり、`std::i32::MAX`のような旧形式の整数定数はdeprecatedになりました。 整数定数には`i32::MAX`のように型に属する定数を使えます。 `transmute_copy`は`?Sized`に対応し、失敗時のpanicはunwindしなくなっています([1.99.0の互換性に関する変更](https://doc.rust-lang.org/stable/releases.html#compatibility-notes))。
`Pin::new_unchecked`では、pointerに実装するsafe traitも安全性の条件に関わります。 `Deref`や`DerefMut`は常に同じobjectを指し、pointerの破棄時にも中身を移動させない必要があります。 `Clone`による複製先も、`Pin`として安全に扱えるpointerでなければなりません([安全性の条件を変更したPR #156935](https://github.com/rust-lang/rust/pull/156935))。
使い切った`RangeInclusive`の`start`と`end`は、特定の値になる保証がありません。 反復終了後の境界値を読み、終了前の範囲や最後の値を復元する前提にはできません([標準libraryの変更](https://doc.rust-lang.org/stable/releases.html#libraries))。
### `Pin`の保証をpointer側のsafeな操作でも壊さない
`Pin`は、pointer型`P`の操作がpointeeを移動させないという条件にも依存します。 `Pin::new_unchecked`を呼ぶ時点で、単にpointerが有効なallocationを指していれば済むわけではありません。 `P`の`Deref`が呼ぶたびに異なるobjectを返す実装なら、固定したつもりのobjectと、後で参照するobjectが一致しなくなります。 `DerefMut`や`Drop`がpointeeを移動させる実装も、固定した位置へ依存する値の前提を壊します([Pin::new\_uncheckedのSafety](https://doc.rust-lang.org/std/pin/struct.Pin.html#method.new%5Funchecked))。
`Clone`の条件は、複製したpointerを再び`Pin`として使う場面に関わります。 複製先が、元のpointeeを移動するような所有者へ変わる実装であれば、複製する操作はsafeでも、構築した`Pin`の保証は維持できません。 1.99の修正では、safe traitの実装も`Pin`を成立させる契約の一部である点を明確にしました。 標準のpointerと独自のpointerを同一視せず、`Pin`を作るunsafeな境界で、後から呼べる操作まで保証に含めます([契約を整理したPR #156935](https://github.com/rust-lang/rust/pull/156935))。
`Pin`は、pointeeの中の任意のfieldをすべて自動的に固定する構文でもありません。 固定の保証を引き継ぐstruct fieldの範囲は、projectionを公開する側の設計に依存します。 今回のpointerに関する条件を満たしても、別のunsafeなprojectionでfieldを移動させれば、field側の保証は壊れます。 反対に、`Unpin`な値は移動を禁止する必要がないため、すべての`Pin`利用に同じ制約を追加する説明も正確ではありません([Pinのstructural pinning](https://doc.rust-lang.org/std/pin/index.html#projections-and-structural-pinning))。
### 使用済みの範囲と、宣言した上限を同じ状態へ保存しない
`RangeInclusive`は、両端を保持する値であると同時に、反復すると内部状態が進むiteratorです。 たとえば上限へ到達した後の値から`start()`を読んで、最初に指定した下限を復元する使い方は、もともとの入力を別に保持する実装とは異なります。 反復の途中では残っている範囲を表し、反復後は使い切った状態になります。 1.99では反復処理を最適化した結果、使い切った後の境界値が以前と異なる場合があります([RangeInclusiveの終了後の状態](https://doc.rust-lang.org/std/ops/struct.RangeInclusive.html))。
`is_empty()`とiteratorとしての終了状態を扱うこと、使い切った後の特定の境界値を期待することは別の契約です。 最初の下限や上限を後で記録する処理なら、反復前に元の値を保存した実装は、終了後の内部表現に依存しません。 sliceのindexとして使い切った範囲を再利用していた処理では、反復前の範囲と同じ対象を指すと仮定できなくなります。 最適化で変更されたのは、保証のなかった終了後の境界値です。 `a..=b`の範囲が上限を含む規則は変わりません。
## pathから時刻を設定し、symbolic linkの追跡を選ぶ
`File::set_times`は、すでに開いているfileへ時刻を設定するAPIです。 1.99で加わった`fs::set_times`はpathを受け取り、対象のfileやdirectoryへ時刻を設定します。 `fs::set_times_nofollow`は、pathの最後がsymbolic linkなら、参照先ではなくlink自身へ適用します。 たとえばarchiveからlinkを復元する処理では、参照先の更新時刻を変えず、linkの時刻を復元する操作を選べます([set\_times](https://doc.rust-lang.org/std/fs/fn.set%5Ftimes.html)、[set\_times\_nofollow](https://doc.rust-lang.org/std/fs/fn.set%5Ftimes%5Fnofollow.html))。
時刻の指定には`FileTimes`を使います。 `FileTimes::new()`では変更する時刻が指定されておらず、`set_modified`だけを呼べば更新時刻を選んだ値にします。 最終アクセス時刻まで同じ値にしたい場合は、`set_accessed`も明示して組み立てます。 更新時刻を設定するためにfileの内容を書き直す操作ではないので、内容のbyte列と、変更するmetadataを別に扱えます([FileTimesの各fieldの指定](https://doc.rust-lang.org/std/fs/struct.FileTimes.html))。
darwin/aarch64では、同じsymbolic linkのpathから、参照先とlink自身の更新時刻を別々に設定できます。 参照先の時刻は`metadata`、link自身の時刻は`symlink_metadata`で取得します。
```rust
use std::fs::{self, FileTimes};
use std::time::{Duration, UNIX_EPOCH};
fn main() -> std::io::Result<()> {
let root = std::env::temp_dir().join(format!("rust199-times-{}", std::process::id()));
fs::create_dir(&root)?;
let target = root.join("target");
let link = root.join("link");
fs::write(&target, b"content")?;
std::os::unix::fs::symlink(&target, &link)?;
let first = UNIX_EPOCH + Duration::from_secs(86_400);
let second = UNIX_EPOCH + Duration::from_secs(172_800);
fs::set_times(&link, FileTimes::new().set_modified(first))?;
fs::set_times_nofollow(&link, FileTimes::new().set_modified(second))?;
assert_eq!(fs::metadata(&target)?.modified()?, first);
assert_eq!(fs::symlink_metadata(&link)?.modified()?, second);
assert_eq!(fs::read_to_string(&target)?, "content");
fs::remove_file(link)?;
fs::remove_file(target)?;
fs::remove_dir(root)
}
```
新しいAPIは、pathへ指定した対象に対してOSの時刻設定機能を呼び出します。 Unixでは`AT_SYMLINK_NOFOLLOW`を指定した`utimensat`、Appleでは`FSOPT_NOFOLLOW`を指定した`setattrlist`に対応します。 Windowsでの現在の実装は`SetFileTime`に対応します。 権限が足りない場合やOSで操作を支えていない場合にはerrorを返します。 時刻設定を無視して成功するAPIではありません([set\_times\_nofollowのplatform別の動作とerror](https://doc.rust-lang.org/std/fs/fn.set%5Ftimes%5Fnofollow.html#platform-specific-behavior))。
時刻の解像度はfile systemに依存するため、任意の細かい値を指定すれば必ず同じ精度で保存されるとまでは言えません。 比較するtimestampは秒単位で、file systemが保持する精度を越える小数部は含みません。 APIのlink追跡の有無と、保存できる時刻の精度は別の条件です。 開いたhandleが表すobjectへ設定する処理と、pathを再解決して設定する処理では、対象を確定する時点が異なります。 解決するまでにpathの指す対象が変われば、設定先のobjectも変わり得ます。
## compilerの診断で変更になる入力
`no_mangle`は、外部から参照するsymbol名をRustの通常のmanglingから外す属性です。 generic functionは、具体的な型やconst argumentごとに実装を生成するため、複数の実装へ同じ固定symbol名を与える契約とは合いません。 1.99では、generic itemへの`no_mangle`がwarningからhard errorになりました。 `identity`は具体化した型ごとにsymbol名を分ける必要があるため、`#[unsafe(no_mangle)]`自体をerrorにします。 診断は、型またはconstのgeneric functionにmanglingを要求します。
```rust
#[unsafe(no_mangle)]
pub fn identity(value: T) -> T { value }
// error: functions generic over types or consts must be mangled
```
呼び出しを`identity(1_u32)`の1個に限っても、generic itemへ付けた属性は許可されません([hard error化したPR #154585](https://github.com/rust-lang/rust/pull/154585))。
FFIへ公開するentry pointが固定された型を受けるなら、genericな内部実装と、具体的な型を持つ外部向けfunctionを分けられます。 属性を削除するだけで外部の呼び出し元が同じsymbolを発見できるようになるわけではありません。 外部向けの名前が必要な場所を、具体的なABIを持つfunctionへ限定する変更になります。 外部へ公開する実装を型と名前で確定させ、genericな内部実装を呼び出すwrapperを分けます。
`unconditional_panic`の診断対象には、sizeが0だと必ずpanicする`chunks`や`windows`の呼び出しも加わりました。 sliceを空にする操作と、chunkの大きさを0にする操作は異なります。 空のsliceをsize 1で分割する呼び出しなら、単に要素がない結果ですが、size 0は分割方法として許可されません。 1.99では、明らかにsizeが0になる呼び出しを、panicへ到達する前に診断できる範囲が広がっています([unconditional\_panicを拡張したPR #153563](https://github.com/rust-lang/rust/pull/153563))。
旧形式の整数moduleのdeprecated化は、定数の数値を変える変更ではありません。 `std::i32::MAX`という経路から、整数型自身のassociated constantである`i32::MAX`へ移します。 型のassociated constantを選ぶことで、旧moduleをimportして定数を参照する形を減らせます。 `deny(warnings)`を設定するbuildでは、deprecatedの診断でbuildが失敗します。 数値の変更を伴わなくても、参照経路を修正する必要があります([std::i32のdeprecated属性](https://doc.rust-lang.org/std/i32/index.html))。
## 1.99.1で修正されたABIと配布物
1.99.1では、x86\_64でCへ`bool`を返す際のzero extensionが復旧しました。 1.99.0では戻り値のbit 1からbit 7が未規定になり、CのABIが求める値の表現を満たしていませんでした。 FFIの戻り値の表現に関わり、Rustの型検査では取り除けないregressionでした([1.99.1のbool ABI修正](https://blog.rust-lang.org/2026/10/15/Rust-1.99.1/))。
残る2件は、LLVMのloop idiom認識で必要になった`wcslen`のfallbackと、macOS配布物の`rust-lld`です。 `wcslen`の欠落はUEFI向けの`std`などに影響していました。 macOSでは、`rust-lld`の直接起動時に`libLLVM`を発見できない場合がありました。 rustup proxyを経由しない起動を修正するため、rpathが追加されています([1.99.1の修正内容](https://blog.rust-lang.org/2026/10/15/Rust-1.99.1/))。
`bool`のregressionでは、Rust側のtrueとfalseの意味を変更したのではなく、返り値をABI上のbit列へ変換する処理が欠けていました。 x86\_64の当該規約では、少なくとも低位8 bitのうちbit 1からbit 7を0にする必要があります。 bit 0だけが論理値に合っていても、呼び出し元が低位8 bitを返り値として使えば、期待する0または1と一致しない値になります。 Rustのfunction本文が正しくても、呼び出し境界の命令がABIを満たさなければ、外部のcodeへ不正な値を返します([ABI不一致を報告したissue #163911](https://github.com/rust-lang/rust/issues/163911))。
`wcslen`の欠落では、元のRustコードが`wcslen`を直接呼んでいなくてもlink errorになり得ました。 LLVMが文字列の長さを数えるloopを認識し、library functionへの呼び出しへ置き換えるためです。 UEFIの報告では、release buildのlink時に`undefined symbol: wcslen`が現れています。 compilerが生成した呼び出しを受けるfallbackをtarget側に用意する必要があります。 fallbackの欠落は、型検査やobject fileの生成後、link段階の失敗として現れます([UEFIでのlink errorを報告したissue #163614](https://github.com/rust-lang/rust/issues/163614))。
macOSの`rust-lld`は、tool自身が必要とする`libLLVM`の探索経路に問題がありました。 rustup proxyを経由した起動では環境によってlibraryを発見できても、toolchain内のcompilerを直接起動すると探索に失敗する場合がありました。 修正版は配布するtoolのrpathを補い、`rust-lld`が自身の依存libraryを見つける経路を持たせています。 アプリケーション自身がlinkするlibraryを追加する修正とは異なり、linkerを起動するまでの配布物の依存関係に関わる修正です([macOS配布物を修正したPR #163954](https://github.com/rust-lang/rust/pull/163954))。
### Go 1.27の変更と設計背景
URL: https://www.sakashita.page/go-1-27-release-notes-and-analysis/
Last updated: 2026-10-25T04:06:22.000Z
Go 1.27.0は2026年8月19日に公開されました。 2026年10月24日までに公開された1.27.3までを対象に、言語、JSON、runtimeの変更を解説します。 1.27.0ではgeneric method、JSON v2、goroutineリークプロファイル、割り当ての特殊化、SIMDなどが加わっています。
続く1.27.1は9月1日公開で、cgo、compiler、runtime、go fixの不具合を修正しています。 ライブラリ側の修正対象は、database/sql、debug/elf、encoding/json、net/http、os、simd、simd/archsimdです。 1.27.2は10月8日公開で、goコマンド、TLS、テンプレート、HTTP、MIMEヘッダー、osのセキュリティ修正を含みます。 compiler、linker、runtime、各種ツールとライブラリの不具合修正も入っています。 1.27.3は10月19日公開で、crypto/x509の証明書チェーン検証を修正しています([Go公式リリース履歴](https://go.dev/doc/devel/release#go1.27.0))。
## generic methodの追加とinterfaceへの制約
Go 1.27では、methodがreceiverのtype parameterとは別のtype parameterを宣言できます。 次のプログラムでは、non-generic typeの`Registry`に、`int`または`string`を受け取るmethodを定義しています([accepted proposal #77273](https://github.com/golang/go/issues/77273))。
```go
package main
import "fmt"
type Registry struct {
prefix string
}
func (r Registry) Label[T ~int | ~string](v T) string {
return fmt.Sprintf("%s:%v", r.prefix, v)
}
func main() {
r := Registry{prefix: "item"}
fmt.Println(r.Label(42))
fmt.Println(r.Label("ready"))
}
```
Go 1.27.3のdarwin/arm64で実行すると、type argumentは呼び出しごとに推論され、次の出力になります。
```text
item:42
item:ready
```
`r.Label(42)`では、receiverが`Registry`、type argumentが`int`と、呼び出し箇所で決まります。 しかし、generic methodをinterface経由で呼べると仮定すると、receiverの具体型が実行時まで確定しない場合があります。 具体型とtype argumentが別packageに分かれると、直接呼び出しだけでは必要なinstantiationを集められません([Type Parameters Proposal](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#No-parameterized-methods))。
### 2021年のproposalで問題になった4つのpackage
2021年のType Parameters Proposalで、著者の[Ian Lance Taylor](https://github.com/ianlancetaylor)と[Robert Griesemer](https://github.com/griesemer)は、4つのpackageを使った例を示しています。 掲載例は、proposalの例の名前と戻り値を変え、必要な部分を抜き出しています。 `contracts.Echoer`の宣言は、当時検討していた「type parameterを持つinterface method」の仮想構文です。 Go 1.27でも、`contracts.Echoer`のinterface宣言は使えません([Type Parameters Proposal](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#No-parameterized-methods))。

providerはreceiverの型、consumerはtype argumentを持ち、必要なHolderとintの組み合わせがpackage間に分かれている仮想例
```go
// provider/provider.go
package provider
type Holder struct{}
func (Holder) Echo[T any](value T) T { return value }
```
```go
// contracts/contracts.go
package contracts
type Echoer interface {
Echo[T any](T) T
}
```
```go
// consumer/consumer.go
package consumer
import "example.test/demo/contracts"
func Extract(value any) int {
if receiver, ok := value.(contracts.Echoer); ok {
return receiver.Echo[int](7)
}
return 0
}
```
```go
// entry/entry.go
package entry
import (
"example.test/demo/consumer"
"example.test/demo/provider"
)
var Result = consumer.Extract(provider.Holder{})
```
`entry`が渡す値は`provider.Holder{}`なので、`consumer.Extract`内の`Echo[int](7)`は`Holder.Echo[int]`の呼び出しになります。 `Holder`と`int`の組み合わせが必要ですが、`consumer`のコンパイル時に見えるのは`int`だけで、引数の実体が`Holder`だとはわかりません。 methodの本体を持つ`provider`にも`Echo[int]`の呼び出しはなく、定義だけから必要なtype argumentを選び出すことはできません。 両packageをimportする`entry`も`Echo[int]`を直接呼ばないため、packageごとの直接呼び出しだけでは必要なinstantiationを集められません。
必要な`Holder.Echo[int]`を生成するには、package内の直接呼び出し以外から、型の組み合わせを集める仕組みが必要です。 TaylorとGriesemerは、リンク時に組み合わせを調べる方式と、実行時に処理する2つの方式を検討しています。
リンク時にinstantiationするなら、プログラム全体の呼び出し関係から、型とtype argumentの組み合わせを探します。 しかし、実行時の文字列を使ってmethodをreflectionで検索する場合、静的な呼び出し関係だけでは候補を限定できません。
実行時にinstantiationする案は、JITなどのコード生成機構を追加し、必要な組み合わせの実装を生成します。 TaylorとGriesemerは、実装が複雑になり、実行時にも遅くなると指摘しています。 reflectionによる汎用実装も、具体型ごとの事前生成を避ける代わりに、実行時に型を調べる処理が必要です。 著者らは、汎用実装にも複雑さと実行速度の問題があると説明しています([Type Parameters Proposalの実装案](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#No-parameterized-methods))。
### 採用されたのは、具体型を静的に解決できる呼び出し
Go 1.27で使えるgeneric methodは、具体型に対して呼び出すもので、interface methodを実装しません。 具体型のmethodであれば、receiver付きのgeneric functionとして実装できます。 Robert Griesemerは、interface実装とは別に、型の名前空間と`x.a().b()`の構文にもmethodの用途があるとして、[proposal #77273](https://github.com/golang/go/issues/77273)を提出しました。
冒頭の`r.Label(42)`なら、receiverの`Registry`とtype argumentの`int`を呼び出し箇所で解決できます。 概念上は、receiverを第1引数に移し、`func label[T ~int | ~string](r Registry, v T) string`への呼び出しとして扱えます。 receiverを通常の引数として扱えば、動的な受け手の列挙は不要になります。 具体型への呼び出しは、既存のgeneric functionと同じ仕組みでinstantiationできます([proposal #77273のImplementation節](https://github.com/golang/go/issues/77273))。
interface methodを実装しない制約は、代入の可否にも現れます。 たとえば、次の宣言では`Registry`を`IntLabeler`へ代入できません。
```go
type IntLabeler interface {
Label(int) string
}
var _ IntLabeler = Registry{}
```
`Label[int]`という呼び出しが可能でも、型`Registry`のmethod宣言は`Label[T ...](T) string`のままです。 `Label(int) string`へ自動的に固定してinterfaceを満たす規則はありません。 `var _ IntLabeler = Registry{}`は、Go 1.27.3でもmethodの型が一致せず、コンパイルエラーになります。 instantiation前のgeneric methodをreflectionから名前や番号で取得する操作も対象外です([proposal #77273](https://github.com/golang/go/issues/77273))。
標準ライブラリでの採用例は、`math/rand/v2`の`*Rand`に加わった`N[Int intType](Int) Int`です。 従来のpackage function`rand.N`と同じ型の整数範囲を、個別の乱数生成器から生成できます([math/rand/v2.Rand.N](https://pkg.go.dev/math/rand/v2@go1.27.0#Rand.N))。
## JSON v2のAPIと互換性
Go 1.27では、`GOEXPERIMENT`環境変数を設定せずに`encoding/json/v2`と`encoding/json/jsontext`をimportできます。 既存の`encoding/json`も、内部ではv2の実装を使うようになりました。 ただし旧APIは互換オプションを使うため、importパスを変えなければ、既定動作までv2へ切り替わるわけではありません([encoding/jsonの移行仕様](https://pkg.go.dev/encoding/json@go1.27.0#hdr-Migrating%5Fto%5Fv2))。
### なぜ既存のAPIを修正するだけでは足りなかったのか
旧APIで型ごとのJSON表現を定めるには、`MarshalJSON() ([]byte, error)`で完全なJSONのバイト列を返します。 method側は返却用のバイト列を組み立て、呼び出したJSON実装は、受け取った内容を再検査して出力形式を調整します。 完全なバイト列を返す契約があるため、methodとJSON実装の間で中間データの生成と走査が発生します([JSON v2の設計背景](https://go.dev/blog/jsonv2-exp))。
読み込み側の`UnmarshalJSON([]byte) error`も完全なJSON値を要求するので、呼び出し元は値の終端まで走査してからmethodへ渡します。 methodは受け取ったJSONを再び解析するため、再帰的な変換では、階層を下るたびに同じ範囲の一部を走査し直します。 内部実装だけを交換しても、完全な値を受け渡すmethodの契約を保つ限り、再走査の原因は残ります。 [Joe Tsai](https://github.com/dsnet)らは、バイト列生成と再走査を既存APIの契約から生じる問題として説明しています([2025年のJSON v2解説](https://go.dev/blog/jsonv2-exp))。
新APIは、完全なバイト列の代わりに、進行中のEncoderやDecoderを型のmethodへ渡します。 書き出しには`MarshalJSONTo(*jsontext.Encoder) error`、読み込みには`UnmarshalJSONFrom(*jsontext.Decoder) error`を使います。 methodは渡されたEncoderへ直接書き、またはDecoderから直接読みます。 変換途中の処理をmethodへ引き継げるため、methodとの境界で値全体を別のバイト列にする必要がなくなります([MarshalerTo](https://pkg.go.dev/encoding/json/v2@go1.27.0#MarshalerTo)、[UnmarshalerFrom](https://pkg.go.dev/encoding/json/v2@go1.27.0#UnmarshalerFrom))。

旧APIは完全なJSON値を切り出してmethodへ渡し、新APIは進行中のDecoderを共有する読み込み処理の比較
EncoderとDecoderが属する`jsontext`は、JSONのtokenや値を読み書きし、配列やobjectの途中状態など、構文の整合性を管理します。 Goのstructなどへ変換する場合は、構文層の上にある`json/v2`が、JSONとGoの値を対応付けます。 反対に、token単位で入力を加工するだけなら、structへ変換せずに`jsontext.Decoder`を使えます([jsontextの概要](https://pkg.go.dev/encoding/json/jsontext@go1.27.0)、[v2のfunction一覧](https://pkg.go.dev/encoding/json/v2@go1.27.0#pkg-index))。
`json/v2`から変換を始める場合も、入出力の形式に応じて入口を選べます。 バイト列なら`Marshal`と`Unmarshal`、io.Writer・io.Readerなら`MarshalWrite`と`UnmarshalRead`を使います。 `jsontext`のEncoder・Decoderを既に使っているなら、`MarshalEncode`と`UnmarshalDecode`へ渡せます。 各functionは可変長の`Options`を受け取るので、入口が異なっても呼び出しごとに動作を指定できます([encoding/json/v2](https://pkg.go.dev/encoding/json/v2@go1.27.0))。
### 新旧の既定動作を、同じ入力で比較する
`encoding/json/v2`は、重複したobject名と不正なUTF-8を既定で拒否します。 旧APIは不正なUTF-8を置換文字へ変えて受け入れ、重複名の処理は変換先の型に依存します。 例えば`{"role":"user","role":"admin"}`を`map[string]string`へ読むと、旧APIでは`admin`を格納します。
nilのスライスとmapをmarshalする場合、旧APIの出力は`null`ですが、v2ではスライスが`[]`、mapが`{}`になります。 field名の照合も変わり、旧APIでは`Name`へキー`name`を読めますが、v2の既定では一致しません。 未知のキーを無視する既定動作によって、v2の`Name`は空のままです([v1とv2の動作比較](https://pkg.go.dev/encoding/json@go1.27.0#hdr-Migrating%5Fto%5Fv2)、[v2のSecurity Considerations](https://pkg.go.dev/encoding/json/v2@go1.27.0#hdr-Security%5FConsiderations))。
次のプログラムは、不正なUTF-8を含む入力もバイト列で明示して、新旧の結果を比較します。
```go
package main
import (
jsonv1 "encoding/json"
jsonv2 "encoding/json/v2"
"fmt"
)
func main() {
for _, input := range []struct {
name string
data []byte
}{
{"duplicate", []byte(`{"role":"user","role":"admin"}`)},
{"invalid-utf8", []byte{'"', 0xff, '"'}},
} {
var a, b any
e1 := jsonv1.Unmarshal(input.data, &a)
e2 := jsonv2.Unmarshal(input.data, &b)
fmt.Printf("%s: v1-ok=%t v2-ok=%t\n", input.name, e1 == nil, e2 == nil)
}
var a, b struct{ Name string }
if err := jsonv1.Unmarshal([]byte(`{"name":"Go"}`), &a); err != nil {
panic(err)
}
if err := jsonv2.Unmarshal([]byte(`{"name":"Go"}`), &b); err != nil {
panic(err)
}
fmt.Printf("case: v1=%q v2=%q\n", a.Name, b.Name)
for _, value := range []any{[]int(nil), map[string]int(nil)} {
old, err := jsonv1.Marshal(value)
if err != nil {
panic(err)
}
current, err := jsonv2.Marshal(value)
if err != nil {
panic(err)
}
compat, err := jsonv2.Marshal(value, jsonv1.DefaultOptionsV1())
if err != nil {
panic(err)
}
fmt.Printf("nil: v1=%s v2=%s compatibility=%s\n", old, current, compat)
}
}
```
Go 1.27.3での出力は次のとおりです。
```text
duplicate: v1-ok=true v2-ok=false
invalid-utf8: v1-ok=true v2-ok=false
case: v1="Go" v2=""
nil: v1=null v2=[] compatibility=null
nil: v1=null v2={} compatibility=null
```
末尾の`compatibility=null`は、同じv2の実装でも、`jsonv1.DefaultOptionsV1()`を渡すと旧APIのnil表現になることを示しています。 nilの表現だけを変えるなら、`FormatNilSliceAsNull(true)`や`FormatNilMapAsNull(true)`を選べます。 名前の対応付けには`MatchCaseInsensitiveNames(true)`、未知のfieldの拒否には`RejectUnknownMembers(true)`があります([v2のOptions](https://pkg.go.dev/encoding/json/v2@go1.27.0#Options))。
Go 1の互換性文書では、互換性を保証する対象を次のように定めています。
> Compatibility is at the source level.
出典:[Go 1 and the Future of Go Programs](https://go.dev/doc/go1compat)。
互換性の対象は、ソースコードの水準でコンパイル・実行できることです。 不具合修正、未規定の動作、仕様上の誤り、セキュリティ、標準ライブラリのstructをキーなしで初期化するコードなどは例外です。 JSONでは、旧APIの振る舞いを互換オプションで残し、新しいimport先でnil表現や入力拒否の既定値を変える形を採っています([互換性文書のExpectations](https://go.dev/doc/go1compat#expectations)、[v1の移行仕様](https://pkg.go.dev/encoding/json@go1.27.0#hdr-Migrating%5Fto%5Fv2))。
### 実験版のサンプルから変わったAPI
Go 1.25以降の実験版からは、タグやmethodの契約も変わっています。 `inline`タグは`embed`へ改名され、`format`タグ、`unknown`タグ、`DiscardUnknownMembers`は削除されました。 独自のJSON処理から標準動作へ委ねる指定も、削除された`SkipFunc`から`errors.ErrUnsupported`へ変わっています。 数値を取り出す`jsontext.Token`のmethodは、値に加えてエラーも返します([Go 1.27のJSON変更](https://go.dev/doc/go1.27#jsonv2))。
`string`タグは、スライスなどの複合型へ再帰的に適用されなくなりました。 v2の意味付けでは、複合型への指定は実行時エラーになります([proposal #79065](https://github.com/golang/go/issues/79065))。 `MatchCaseInsensitiveNames`も、複数のfieldが候補になり、完全一致がなければエラーを返します。 以前のように宣言順の最初のfieldを選ぶ動作はなくなりました([曖昧なfield照合を拒否するcommit](https://github.com/golang/go/commit/40f0f8125549b511e41607712acb0435ce07d060))。
`encoding/json`の内部実装を従来のものへ戻すビルド用の指定は、`GOEXPERIMENT=nojsonv2`です。 一方、v2の実装でnilの出力だけを`null`にするには、`FormatNilSliceAsNull(true)`や`FormatNilMapAsNull(true)`を使います。 `GOEXPERIMENT`は旧APIの実装を選び、変換オプションはv2での出力規則を選びます([Go 1.27のJSON変更](https://go.dev/doc/go1.27#jsonv2))。
## goroutineleakの到達可能性による検出
`runtime/pprof`の`goroutineleak` profileは、Go 1.27では`GOEXPERIMENT`環境変数を設定せずに使えます。 HTTPでの取得先は`/debug/pprof/goroutineleak`です([runtime/pprof.Profile](https://pkg.go.dev/runtime/pprof@go1.27.0#Profile))。 `GOEXPERIMENT=goroutineleakprofile`による有効化指定は削除されました([Go 1.27のruntime変更](https://go.dev/doc/go1.27#goroutineleak-profiles))。
GCは、GC rootを起点に参照をたどり、到達したheap objectをmarkして記録します。 通常はすべてのgoroutineがGC rootになるので、回復不能にblockしたgoroutineが保持するmemoryもmarkされます。 通常のmark結果には、リークしたgoroutineからしか参照できないmemoryも、到達可能なものとして残ります。
GC rootを絞るリーク検出方式を、[Georgian-Vlad Saioc](https://github.com/VladSaiocUber)と[Milind Chabbi](https://conf.researchr.org/profile/milindchabbi)が提案しました。 実行可能なgoroutineを最初のGC rootとし、blocked goroutineは起点から外します。 グローバル変数など、goroutine以外の通常のGC rootは残します([goroutineリーク検出のproposal](https://go.googlesource.com/proposal/+/master/design/74609-goroutine-leak-detection-gc.md))。
最初のmarkが終わったら、未markのblocked goroutineについて、channelやMutexなど、blockの原因になっている同期対象を調べます。 同期対象がmark済みなら、到達可能な側の処理によってgoroutineを再開できる可能性があるため、対象のgoroutineもGC rootへ加えます。 新しく加えたGC rootからさらにheapをmarkし、追加できるgoroutineがなくなるまで繰り返します。 反復が終わっても起点へ加えられなかったgoroutineが、リークとして記録されます。
例えば、実行可能な`G0`が`c1`を参照し、`c1`でblockしている`G1`が`c2`を参照し、`G2`が`c2`でblockしているとします。 初期のGC rootから`c2`への参照経路はないものとします。 図では、GC rootから到達できない別のchannel`c3`で、`G3`もblockしていると仮定しています。

実行可能なG0からc1へ到達し、G1とG2を順に起点へ加える一方、c3でblockしたG3は到達可能な集合へ入らない流れ
最初のmarkで到達するのは`c1`までですが、`G1`をGC rootへ加えると`c2`へ到達し、`G2`もGC rootへ加えられます。 初回のmarkだけでリークを判定すると、参照をたどる途中で見つかるgoroutineを誤ってリークに含めるため、反復が必要です([検出アルゴリズムのproposal](https://go.googlesource.com/proposal/+/master/design/74609-goroutine-leak-detection-gc.md))。 反復が終わっても`c3`は到達可能にならないため、`G3`がリークとして記録されます。
同期処理用object`*sudog`は、`allgs`や`semtable`からも到達できます。 参照を無条件にたどると、再開可能な側から到達していないchannelまでmarkされてしまいます。 proposalでは、`*sudog`の`.elem`と`.hchan`を条件付きでたどり、通常のmarkから参照を一時的に隠します([GCと参照表現の変更](https://go.googlesource.com/proposal/+/master/design/74609-goroutine-leak-detection-gc.md))。
次のプログラムは、`orphaned`と`reachable`を、別のchannelの受信でblockさせます。 `orphaned`のchannelはblocked goroutineだけに残す一方、`reachable`のchannelはグローバル変数`held`にも残します。
```go
package main
import (
"bytes"
"fmt"
"runtime"
"runtime/pprof"
"strings"
)
var held chan int
func orphaned(ch <-chan int, ready chan<- struct{}) {
close(ready)
<-ch
}
func reachable(ch <-chan int, ready chan<- struct{}) {
close(ready)
<-ch
}
func startOrphan() {
ch := make(chan int)
ready := make(chan struct{})
go orphaned(ch, ready)
<-ready
runtime.Gosched()
}
func main() {
runtime.GOMAXPROCS(1)
startOrphan()
held = make(chan int)
ready := make(chan struct{})
go reachable(held, ready)
<-ready
runtime.Gosched()
var profile bytes.Buffer
if err := pprof.Lookup("goroutineleak").WriteTo(&profile, 1); err != nil {
panic(err)
}
fmt.Println("orphaned:", strings.Contains(profile.String(), "main.orphaned"))
fmt.Println("reachable:", strings.Contains(profile.String(), "main.reachable"))
}
```
Go 1.27.3のdarwin/arm64で得た結果は次のとおりです。
```text
orphaned: true
reachable: false
```
`reachable`は`held`を通じてGC rootから到達できるため、profileへ列挙されません。 ただし、`main`は`held`への送信やcloseを行わないため、`reachable`の受信は終わりません。 到達できるchannelが実際に使われるかまでは判定しないため、回復しないblockでも参照が残る場合は検出から外れます。
profileを取得しても、リークしたgoroutineやheap objectは強制的に回収されません。 リークと判定したgoroutineからも最後にmarkしてから、通常のsweepへ進みます。 リーク側のmemoryも残るので、goroutineが保持するmemoryを回収せずに診断結果を得られます([検出範囲と資源回収の扱い](https://github.com/golang/go/issues/74609))。
## 小さいheap割り当ての特殊化
Go 1.27のcompilerは、小さいheap割り当てについて、サイズ別に特殊化したruntime functionを呼びます。 16バイトの型をheapへ割り当てる例では、Go 1.27.3のdarwin/arm64で呼び出し先が変わります。
```go
package allocation
type Value struct{ A, B int64 }
func New() *Value { return new(Value) }
```
既定ではsize class別の入口へ進み、`GOEXPERIMENT=nosizespecializedmalloc`で無効にすると`runtime.newobject`を呼びます。
```sh
go tool compile -S allocation.go \
| rg --only-matching 'runtime\.(mallocgcSmallNoScanSC2|newobject)' | sort -u
# => runtime.mallocgcSmallNoScanSC2
GOEXPERIMENT=nosizespecializedmalloc go tool compile -S allocation.go \
| rg --only-matching 'runtime\.(mallocgcSmallNoScanSC2|newobject)' | sort -u
# => runtime.newobject
```
`Value`はpointerを持たない16バイトの型なので、compilerは、走査不要を意味する`NoScan`と、サイズクラス2の`SC2`に対応する入口を選べます。 `runtime.mallocgcSmallNoScanSC2`には、`sizeclass = 2`と`elemsize = 16`が定数として埋め込まれています。 runtimeは埋め込まれた分類を使えるため、割り当てのたびにサイズクラスを決める経路を通らずに済みます([Go 1.27.3のmalloc\_generated.go](https://github.com/golang/go/blob/go1.27.3/src/runtime/malloc%5Fgenerated.go#L1191))。
特殊化の対象は、一部の80バイト未満の割り当てです。 サイズ別のruntime functionを追加するため、バイナリサイズはワークロードに依存せず約60KB増えると公表されています。 `GOEXPERIMENT=nosizespecializedmalloc`はGo 1.27のビルド時に使用できます([Faster memory allocation](https://go.dev/doc/go1.27#faster-memory-allocation))。
## SIMDの可変幅APIとarchitecture固有のAPI
Go 1.27のexperimental package`simd`は、vector幅を型名に固定しないAPIを提供します。 固定幅APIでは、128ビット、256ビット、512ビットの幅に応じてループの処理単位が変わります。 対して、`simd`の呼び出し側は、実際に読み込めた要素数でループを進めます([Portable SIMD proposal #78902](https://github.com/golang/go/issues/78902))。
例えば`Int32s`は、`int32`型の値を複数まとめて保持し、加算などの演算を要素ごとに適用するSIMD vectorです。 vectorの幅は128ビット以上で、プログラムの実行中は全vectorが同じ幅を使うため、処理中に要素数の基準は変わりません。 対応するCPU命令がなければGoで演算を再現するので、命令がない環境でもポータブル版のAPIを使えます([simdの仕様](https://pkg.go.dev/simd@go1.27.0))。
次のプログラムは、`LoadInt32sPart`で実際に読んだ要素数を`n`へ受け取り、`n`だけループを進めて6要素へ10を加算します。 残りがvectorを満たさない場合も、`LoadInt32sPart`は不足分をゼロで埋め、入力から読んだ数を返します。 出力先を`n`要素に絞って`StorePart`へ渡せば、末尾の端数でも、入力に対応する要素だけを書き戻せます([部分ロードとストア](https://pkg.go.dev/simd@go1.27.0#LoadInt32sPart))。
```go
package main
import (
"fmt"
"simd"
)
func main() {
in := []int32{1, 2, 3, 4, 5, 6}
out := make([]int32, len(in))
for i := 0; i < len(in); {
v, n := simd.LoadInt32sPart(in[i:])
v.Add(simd.BroadcastInt32s(10)).StorePart(out[i : i+n])
i += n
}
fmt.Println(out)
}
```
Go 1.27.3、darwin/arm64でのcommandとstdoutは、次の組み合わせです。
```sh
GOEXPERIMENT=simd go run simd.go
```
```text
[11 12 13 14 15 16]
```

4要素幅を仮定した部分ロードの例で、末尾のゼロ埋め部分を保存せず、実際に読んだn要素だけを書き戻す流れ
CPU固有の型と演算を直接使う場合は、ポータブル版に代わって`simd/archsimd`を選びます。 Go 1.27ではamd64向けAPIが改訂され、arm64のNeon 128ビットとWebAssemblyの128ビットSIMDが加わりました。 amd64では、CPUが対応していれば256ビット・512ビットも扱えます。 `simd/archsimd`の有効化にも`GOEXPERIMENT=simd`を使います([simd/archsimd](https://pkg.go.dev/simd/archsimd@go1.27.0)、[Go 1.27の変更範囲](https://go.dev/doc/go1.27#archsimd))。
ポータブル型からCPU固有の演算へ渡すには、`ToArch()`で値を取り出します。 戻り値は`any`なので、対象アーキテクチャのビルド制約を付け、具体型へtype assertionして使います。 逆に、CPU固有の値をポータブル型へ戻す場合は`Int32sFromArch`などを使い、対応しない型を渡すとpanicします([simdのアーキテクチャ間変換](https://pkg.go.dev/simd@go1.27.0#hdr-Conversion%5Fto%5Fand%5Ffrom%5Fsimd%5Farchsimd%5Ftypes%5F))。
## struct literalとtype inferenceの変更
埋め込みfieldから昇格したfieldを、struct literalのキーとして指定できるようになりました([proposal #9859](https://github.com/golang/go/issues/9859))。 generic functionのtype argumentも、対応するfunction typeへの代入・変換全般で推論できるようになりました([proposal #77245](https://github.com/golang/go/issues/77245))。
```go
package main
import "fmt"
type Inner struct{ Port int }
type Outer struct{ Inner }
func keep[T any](value T) T { return value }
func main() {
value := Outer{Port: 8080}
functions := []func(int) int{keep}
fmt.Println(value.Inner.Port, functions[0](7)) // => 8080 7
}
```
`Port`は`Outer`直下の宣言ではなく、`Inner`から昇格したfieldです。 スライスの要素では、期待される`func(int) int`から`keep`のtype argumentを推論します。 同じstruct literalで、昇格したfieldと、昇格元の埋め込みfieldの両方は指定できません([Go 1.27.3のstruct literal検査](https://github.com/golang/go/blob/go1.27.3/src/go/types/literals.go#L194))。
## goコマンド、診断ツール、compilerの変更
### ビルドへの入力と互換設定
compile、link、asm、cgo、cover、packは、`@file`による引数ファイルを受け付けます。 形式はGCC互換で、引用符、エスケープ、バックスラッシュによる行継続を使えます。 モジュール取得では`bzr`のサポートが削除され、bzrサーバーからの直接取得はできなくなりました。
削除済みのGODEBUG設定がgo.modのgodebugや`//go:debug`に残っていても、削除前の最終既定値を指定していれば受理されます。 以前の動作へ戻す値を要求している場合は、ビルドが拒否されます([Goコマンドとツールの変更](https://go.dev/doc/go1.27#tools))。
### テストとコードの検査
`go test`は`stdversion`検査を既定で実行します。 参照元ファイルに適用されるGo版より新しい標準ライブラリのシンボルを使っていれば、検査結果に現れます。 `go test -json`では、outputイベントの`OutputType`に`error`、`error-continue`、`frame`が加わりました。
vetのprintf検査は、値型`E`自身がerrorを実装する場合に、`fmt.Errorf`の`%w`へ`*E`を渡す呼び出しを報告します。 期待するerrorの型と、包んだ値の型の不一致が対象です。 ドキュメントの表示には、`go doc`で`package@version`と`-ex`を使えます。 `-ex`は実行可能な例を列挙し、例の名前からコードとコメントも表示できます([テストと診断ツールの変更](https://go.dev/doc/go1.27#tools))。
### コードと依存宣言の整理
`go fix`には`atomictypes`、`embedlit`、`slicesbackward`、`unsafefuncs`が加わりました。 `fmtappendf`は削除され、`waitgroup`は`waitgroupgo`へ改名されています。 `go mod tidy`は、go 1.27以降のモジュールでrequireブロックを直接依存と間接依存の最大2つに整理し、依存に付いたコメントを保ちます。
gofmtとgo/formatは、CPUアーキテクチャによって異なっていたカラム揃えの判定を修正しています。 整形結果が統一されるため、一部の既存ファイルには空白の差分が生じます([整形とコード更新の変更](https://go.dev/doc/go1.27#tools))。
### runtimeの診断とタイマー
`go tool trace`の`-http=:6060`は、listen先がlocalhostに限定されました。 全アドレスでlistenするには、`-http=0.0.0.0:6060`を指定します。 tracebackは、go 1.27以降のモジュールでgoroutineのpprofラベルをヘッダーに含めます。 ラベルを出さない場合は、`GODEBUG=tracebacklabels=0`を指定できます。
タイマーではasynctimerchanが削除されました。 time packageが作るタイマーのchannelは、常に同期channelになります([Runtimeの変更](https://go.dev/doc/go1.27#runtime))。
### compilerとlinkerが扱う名前
`//line`と`/*line*/`にある相対ファイル名は、ディレクティブを含むファイルのディレクトリから解決されます。 クロージャーは、インライン化によらず単純なシンボル名を使います。 同じfunctionリテラルのコードを共有する場合は、捕捉データが異なるクロージャーでも、コードポインターは一致し得ます。
macOS向けlinkerは、`-macos`と`-macsdk`でLC\_BUILD\_VERSIONのOS版とSDK版を指定できます。 既定値はmacOS 13.0.0とSDK 26.2.0です([CompilerとLinkerの変更](https://go.dev/doc/go1.27#compiler))。
## 標準ライブラリの追加と動作変更
### 署名方式とTLS
`crypto/mldsa`にFIPS 204のML-DSA署名が加わり、x509でも鍵と署名を処理できます。 TLS 1.3では`MLDSA44`、`MLDSA65`、`MLDSA87`を使えます([crypto/mldsa](https://pkg.go.dev/crypto/mldsa@go1.27.0))。 `crypto`には、External μ ML-DSA署名を識別するHash値`MLDSAMu`が加わりました([crypto.MLDSAMu](https://pkg.go.dev/crypto@go1.27.0#MLDSAMu))。
`crypto/ecdsa`では、非nilのSignerOptsをPrivateKey.Signへ渡すと、ハッシュの長さが適切か検査されます([PrivateKey.Sign](https://pkg.go.dev/crypto/ecdsa@go1.27.0#PrivateKey.Sign))。 QUICConfigにはClientHelloInfoConn、ConnectionStateにはLocalCertificateが加わりました。 CurvePreferencesには`MLKEM1024`も指定できます([crypto/tls](https://pkg.go.dev/crypto/tls@go1.27.0))。
TLSのConfig.Randは非推奨になりました。 決定的なテストにはtesting/cryptotest.SetGlobalRandomを使えます。 tlsmlkem=0やtlssecpmlkem=0を指定していても、CurvePreferencesへの明示指定は有効です([tls.Config](https://pkg.go.dev/crypto/tls@go1.27.0#Config))。 互換設定のtlsunsafeekm、tlsrsakex、tls3des、tls10server、x509keypairleafは削除されました。 tlskyberが1.24で削除済みだったことも補記されています([TLSの変更](https://go.dev/doc/go1.27#cryptotls))。
### 証明書の表現と検証
`crypto/x509`の証明書、CSR、CRLにはRawSignatureAlgorithmが加わりました。 未知の署名方式でも、DERのAlgorithmIdentifierを取得できます。 pkix.Nameの未知の属性値も、asn1.RawValueとして保持されます([crypto/x509](https://pkg.go.dev/crypto/x509@go1.27.0))。 `crypto/x509/pkix`のRDNSequence.Stringは、未知のOIDでも、文字列型の属性を文字列として表示します。 従来の表示はDERの16進表現でした([RDNSequence.String](https://pkg.go.dev/crypto/x509/pkix@go1.27.0#RDNSequence.String))。
WindowsとDarwinでは、SSL\_CERT\_FILEかSSL\_CERT\_DIRを指定すると、ファイルからルート証明書を読み込みます。 検証もOSのAPIからGoの検証器へ切り替わり、x509sslcertoverrideplatform=0で無効化できます([SystemCertPool](https://pkg.go.dev/crypto/x509@go1.27.0#SystemCertPool))。
### 値の生成、分割、型変換
新しい`uuid` packageは、UUIDを16バイトの型として扱います。 `New`、`NewV4`、`NewV7`で生成し、`Parse`などで読み込めます([uuid](https://pkg.go.dev/uuid@go1.27.0))。 bytesとstringsには、最後の区切り文字で前後を分ける`CutLast`が加わりました([bytes.CutLast](https://pkg.go.dev/bytes@go1.27.0#CutLast)、[strings.CutLast](https://pkg.go.dev/strings@go1.27.0#CutLast))。
database/sqlの`ConvertAssign`を使うと、Rows.Scanの型変換を利用できます([ConvertAssign](https://pkg.go.dev/database/sql@go1.27.0#ConvertAssign))。 driver側には`RowsColumnScanner`が加わり、利用者の格納先へ直接scanできます([RowsColumnScanner](https://pkg.go.dev/database/sql/driver@go1.27.0#RowsColumnScanner))。
math/bigのInt.Divideは、商と余りを求める演算です。 丸め方向にはTrunc、Floor、Round、Ceilを指定できます([Int.Divide](https://pkg.go.dev/math/big@go1.27.0#Int.Divide))。 hash/maphashには、型ごとのハッシュと同値比較の契約を定めるHasherが加わりました。 `==`で比較する場合はComparableHasherを使えます([hash/maphash](https://pkg.go.dev/hash/maphash@go1.27.0))。
### `Hasher`の同値比較とhashの入力を一致させる
`Hasher[K]`は、`Hash(*Hash, K)`と`Equal(K, K) bool`を持つinterfaceです。 `Equal`がtrueになる2値は、同じseedで同じhash値にならなければなりません。 値が異なってもhash値が衝突することは許されますが、同値な値のhash値が異なる実装は契約違反です。 hash値を使って探索先を選ぶcontainerでは、同値比較へ到達する前に別の場所を探してしまうためです([Hasherの契約](https://pkg.go.dev/hash/maphash@go1.27.0#Hasher))。
たとえば、小文字へ変換した結果で文字列を比較する実装は、hashにも同じ変換結果を渡します。 `Equal`だけで大文字と小文字を区別しなくなっても、元の文字列をhashしていれば、探索先を決める規則と同値比較が一致しません。 `Lowercase`は、比較とhashの両方で`strings.ToLower`を使います。
```go
type Lowercase struct{}
func (Lowercase) Hash(h *maphash.Hash, value string) {
h.WriteString(strings.ToLower(value))
}
func (Lowercase) Equal(left, right string) bool {
return strings.ToLower(left) == strings.ToLower(right)
}
```
同じseedを使えば、比較で等しい`"Name"`と`"name"`のhash値も一致します。
```go
seed := maphash.MakeSeed()
var left, right maphash.Hash
left.SetSeed(seed)
right.SetSeed(seed)
Lowercase{}.Hash(&left, "Name")
Lowercase{}.Hash(&right, "name")
fmt.Println(Lowercase{}.Equal("Name", "name")) // => true
fmt.Println(left.Sum64() == right.Sum64()) // => true
```
`Hasher`はhash値を直接返すAPIではなく、呼び出し側から受け取った`Hash`へ値の表現を書き込みます。 seedを含むhash計算の状態と、型ごとの入力の作り方を分離した形です。 `maphash.MakeSeed`で作るseedは計算の入力なので、異なるseedで作ったhash値の一致は要求しません([Hash.SetSeed](https://pkg.go.dev/hash/maphash@go1.27.0#Hash.SetSeed))。
ただし、`ToLower`を使う比較は、`strings.EqualFold`と同じ比較ではありません。 文字`"ſ"`と`"s"`は、比較方法によって同値性が変わります。
```go
fmt.Println(strings.EqualFold("s", "ſ")) // => true
fmt.Println(Lowercase{}.Equal("s", "ſ")) // => false
```
Unicodeのsimple case foldingには、小文字への変換だけで同じ結果にならない文字の組み合わせがあります。 `EqualFold`へ比較だけを置き換え、hash側へ`ToLower`を残す変更は、同値な文字列のhash値を揃える契約を壊し得ます。 文字列を正規化する場合には、比較とhash入力で採用する文字の同一視まで揃える必要があります([strings.EqualFold](https://pkg.go.dev/strings@go1.27.0#EqualFold))。
新しいinterfaceは、組み込みの`map`へ独自の比較規則を渡す機能ではありません。 組み込みの`map`は引き続きcomparableなkeyと`==`を使います。 `Hasher`を受け取る独自のcontainerなら、key型の比較方法とhash入力を共通のinterfaceで受け取れます。 したがって、`map[string]V`の宣言を変えずに大文字と小文字を同一視する設定が増えたわけではなく、独自のcontainerが公開できる契約の追加です([mapのkeyに関する規則](https://go.dev/ref/spec#Map%5Ftypes))。
### Goコードの解析に使うAPI
go/constantのStringLenは、文字列定数の全体を構築せずに長さを返します([StringLen](https://pkg.go.dev/go/constant@go1.27.0#StringLen))。 go/scannerのScanner.Endはトークンの終端を返し、go/tokenのFile.StringはFileの短い説明を返します([Scanner.End](https://pkg.go.dev/go/scanner@go1.27.0#Scanner.End)、[File.String](https://pkg.go.dev/go/token@go1.27.0#File.String))。
go/typesには、Identicalに対応するHasherと、IdenticalIgnoreTagsに対応するHasherIgnoreTagsが加わりました。 gotypesaliasも削除され、型エイリアスには常にAliasノードが生成されます([go/types](https://pkg.go.dev/go/types@go1.27.0))。
### HTTPと接続の扱い
netのUnixConnは、読み取りがEOFに達すると、net.OpErrorで包まずにio.EOFを直接返します([UnixConn](https://pkg.go.dev/net@go1.27.0#UnixConn))。 HTTPでは、`ConnectionState() tls.ConnectionState`を実装したnet.Connに対応しました。 TransportとServerは、実装から得たTLSの情報を使い、ALPNでプロトコルを選びます([HTTPの変更](https://go.dev/doc/go1.27#nethttp))。
HTTP/2は、RFC 9218のクライアント優先度を受け付けます。 Server.DisableClientPriority=trueを指定した場合は、優先度によらない従来のラウンドロビン処理を使います([http.Server](https://pkg.go.dev/net/http@go1.27.0#Server))。 ヘッダー値の数はServer.MaxHeaderValueCountで制限でき、0ではDefaultMaxHeaderValueCountを使います([MaxHeaderValueCount](https://pkg.go.dev/net/http@go1.27.0#Server.MaxHeaderValueCount))。
HTTP/1のResponse.BodyをCloseすると、未読本文の読み捨てを試みます。 閾値は256KiB、待機期限は50msで、EOFへ到達した場合に接続を再利用します([Go 1.27.3のtransport.go](https://github.com/golang/go/blob/go1.27.3/src/net/http/transport.go#L2417))。 net/urlには、深いコピーを作るURL.CloneとValues.Cloneが加わりました([URL.Clone](https://pkg.go.dev/net/url@go1.27.0#URL.Clone)、[Values.Clone](https://pkg.go.dev/net/url@go1.27.0#Values.Clone))。
### テストと実行環境
net/http/httptestのNewTestServerは、synctestで使えるmemory上の仮想ネットワークを提供します([NewTestServer](https://pkg.go.dev/net/http/httptest@go1.27.0#NewTestServer))。 testing/synctestのSleepは、time.Sleepとsynctest.Waitを結合した操作です([synctest.Sleep](https://pkg.go.dev/testing/synctest@go1.27.0#Sleep))。
runtime/secretでは、secretモード中に作ったgoroutineもsecretモードを継承します([runtime/secret](https://pkg.go.dev/runtime/secret@go1.27.0))。 compress/flateではエンコーダーの変更に伴い、圧縮後のバイト列も変わる場合があります。 DEFLATEを使うzip、gzip、zlib、PNGの出力にも影響します([flateの変更](https://go.dev/doc/go1.27#compressflate))。
### OSと文字データの更新
Plan 9のsyscallには、errorを実装するErrno型が加わりました。 Errnoを参照する共通コードをビルドできますが、システムコールの返り値は従来のErrorStringを保ちます([syscallの変更](https://go.dev/doc/go1.27#syscall))。 unicodeの文字テーブルと関連処理は、Unicode 15から17へ更新されました([unicode.Version](https://pkg.go.dev/unicode@go1.27.0#Version))。
macOSの最低要件は13 Venturaになりました。 Linuxのビッグエンディアンppc64はELFv2 ABIへ移行し、cgo、PIE、外部リンクをサポートします。 ELFv2対応は通常Linux 3.13以降で、RHEL 7には3.10へのバックポートがあります。 cgoや外部リンクを利用する場合は、リンク・ロードするlibcなどのライブラリにもELFv2対応が必要です([Portsの変更](https://go.dev/doc/go1.27#ports))。
## 1.27.2と1.27.3の修正で変わる処理
### JSONの互換層の修正
1.27.2では、encoding/jsonで、名前付き文字列型をmapのキーにした場合の互換性を修正しています。 Go 1.26では名前付き文字列型のキーのMarshalTextを呼んでいませんでしたが、v2実装への切り替えで呼ぶようになっていました。 1.27.2の互換層は`MarshalText`を呼ばず、Go 1.26と同じ結果を返します([JSONのmap key互換性を戻すCL 827864](https://go-review.googlesource.com/c/go/+/827864))。
### 1.27.2のセキュリティ修正
HTTP/2からHTTP/1へ転送するプロキシは、不正なフレーミング関連ヘッダーを削除するようになりました。 不正なヘッダーの転送によるresponse smugglingを防ぎます([不正なHTTP headerを除去するcommit](https://github.com/golang/go/commit/072779d81582543bf2872c259e0c6cceea728de4))。 html/templateも、各JavaScript式の先頭の正規表現を正しくエスケープできるよう、式の文脈をリセットします([JavaScript式の文脈を初期化するcommit](https://github.com/golang/go/commit/b26d150e6985f804826f4fcb1aa33aa81e700b52))。 キーワードとしてのyieldの後は正規表現と判定し、キーワードではない使い方は同じ扱いにしません([yield後の正規表現判定を直すcommit](https://github.com/golang/go/commit/6e2cfab0a1c1683c52f9e09270627b0c4e6446e3))。
モジュール取得では、golang.org/fips140向けの特例がなくなり、正規のgo.sumのh1ハッシュがfips140.sumへ加わりました([FIPSのh1 hashを追加するcommit](https://github.com/golang/go/commit/1e4f1fec6fb468a23979823b8c3cbec5c8fca1af))。 golang.org/toolchainは、sumdbから正規のチェックサムを取得して検証します。 local cacheのチェックサム記録が一致していても、sumdbとの照合を省略しません([toolchainのsumdb検証を必須にするcommit](https://github.com/golang/go/commit/68fa7699a27d745f90bf8630202e1a415d1b0769))。
TLSのECHは、不正なouter extensionの参照を拒否し、各拡張を高々一度だけコピーします([ECHの拡張参照を検証するcommit](https://github.com/golang/go/commit/f022e61963529d5e691f0e42a87fd5b520f01636))。 MIMEヘッダーでは、memory上限から管理用の400バイトを引いた結果が負になると、先頭行だけ無制限として扱われていました。 1.27.2では、負の値でもmemory上限を迂回しないように修正しています([MIMEのmemory上限を適用するcommit](https://github.com/golang/go/commit/7c79b50a74b6d1aa4269b1827dda7d81f3717f9d))。
CONNECTの修正では、Transportが応答にかかわらずCONNECT後の接続を閉じ、ReverseProxyがCONNECTを405で拒否します。 旧動作では、サーバーがCONNECTを拒否すると、送信したRequest.Bodyが接続上の次のHTTP要求として解釈される可能性がありました。 接続をプールへ戻していたため、次に送った要求が先行する要求への応答を読む、という取り違えが起きていました([CONNECT後のconnection poolを保護するcommit](https://github.com/golang/go/commit/76875df0f3425f69e4dc2c439040a248b6107854))。 HTTP/1サーバーも、ハンドラーが2xxを返して接続をhijackせず戻った場合、CONNECTの接続を通常のHTTP要求へ再利用しません([CONNECT後の接続再利用を止めるcommit](https://github.com/golang/go/commit/e92229e24a387cc006a750af176b07a459ba71a1))。
Windowsのos.Rootは、Root.MkdirやRoot.MkdirAllの対象がリンク先の存在しないjunctionだった場合の処理を修正しています。 ルート外へのディレクトリ作成を防ぐ修正です([Windowsのjunction処理を直すcommit](https://github.com/golang/go/commit/9636ab0f172bea01345770c1404d38ed4e1e4d08))。 ServeContentのRange処理も、大量の小さい範囲指定によるCPUとmemoryの消費を制限します。 既定の上限は200範囲で、httpservecontentmaxrangesで変更できます([Rangeの処理量を制限するcommit](https://github.com/golang/go/commit/ababc3cd873d8acce1777268aa7a817e6b672028))。
### HTTP/2の状態管理と処理量
HTTP/2の初期ウィンドウ変更は、SETTINGSの変更ごとに全ストリームを走査する処理から、O(1)の処理へ変わりました。 各ストリームは初期値を保存せず、WINDOW\_UPDATEによる増分と送信済みバイト数の差を保持します。 現在のウィンドウは、増分と送信済みバイト数の差に、現在の初期値を足して求めます。 初期値をストリーム別に保存しないため、SETTINGSの受信時に全ストリームの値を書き換える必要がなくなります。 初期値変更時に全ストリームを検査する代わりに、個々の送信時やWINDOW\_UPDATEの処理時に上限超過を検出します。
上限を管理する別案として、実装者の[Damien Neil](https://github.com/neild)は、ストリームをheapで管理する方式も検討しています。 しかし、heapの管理で実装が複雑になり、処理量も増えるため採用していません。 最大値だけを追跡する方式も、ストリームの作成・更新・resetと初期値変更を組み合わせる攻撃を防げないとして退けています([HTTP/2の初期window更新を変えたcommitの設計理由](https://github.com/golang/go/commit/1d58cdaa1069e259b60c324ec77472f09f23d92e))。
受信側では、ストリームのreset時と、reset後のハンドラーによる読み取り時に、同じデータのフロー制御枠を二重返却する問題を修正しています([受信枠の二重返却を防ぐcommit](https://github.com/golang/go/commit/31dc9e2449e394439498142451627af52626d7c4))。 HPACKも、応答ヘッダーのエンコードと、SETTINGSによるテーブルサイズの変更を並行して行わなくなりました([HPACKの並行更新を防ぐcommit](https://github.com/golang/go/commit/12acaf3086437e140e2dd3b7d2c30fdd633c5fe0))。 Trailerでは、宣言したfieldによるヘッダー数とサイズの上限の迂回を防ぎ、memory確保を制限します([Trailerにもheader上限を適用するcommit](https://github.com/golang/go/commit/cbb0fb8b51cba866018b8a0fb4b5458f16c5c80b))。
### Go 1.27.3で拒否する中間証明書
1.27.3の修正は、証明書のSubjectPublicKeyInfoをSHA-256でハッシュし、拒否対象の公開鍵ハッシュと照合します。 対象は8個のハッシュとして実装に保持されています。 拒否対象の公開鍵を持つ証明書がチェーンに含まれていれば、照合したチェーンを有効な候補から外します。 Go自身の検証器だけでなく、DarwinやWindowsのシステム検証器が返したチェーンにも適用します。 Windowsで有効なチェーンが残らなければ、検証は失敗します([中間証明書を拒否するcommit](https://github.com/golang/go/commit/90617fb7ceb59cf08bee7826bb51b8c8fb8c11de))。
脆弱性の登録は[CVE-2026-111943](https://www.cve.org/CVERecord?id=CVE-2026-111943)、Go側のadvisoryは[GO-2026-6809](https://pkg.go.dev/vuln/GO-2026-6809)です。 Go 1.27系では、`crypto/x509.Certificate.Verify`が対象となり、1.27.3で対処されています。 照合には、証明書全体のDERではなく、SPKIのDERをハッシュした値を使います。 したがって、subjectや有効期限が異なる証明書でも、SPKIのbyte列が一致すれば、同じ拒否対象のハッシュに一致します([crypto/x509の検証実装](https://github.com/golang/go/blob/go1.27.3/src/crypto/x509/verify.go))。
### 日本各地旅行索引:按都道府县查找游记
URL: https://www.sakashita.page/japan-prefecture-travel-guide/
Last updated: 2026-10-24T17:29:59.000Z
这份索引按地区和都道府县整理旅行记录。 从城市散步、温泉到季节风景,可以从下面的地区进入具体的游记。
## 北海道
北海道的旅行记录既有函馆、小樽等城市,也有神威岬、富良野和温泉之乡。 从城市散步到自然风景,可以按兴趣继续阅读下面的游记。
- [网走一日游:博物馆、流冰与原生花园](https://www.sakashita.page/abashiri-travel-guide/)
- [神威岬与余市的自驾之旅](https://www.sakashita.page/cape-kamui-and-yoichi/)
- [薰衣草花季相约富良野](https://www.sakashita.page/furano-lavender-fields/)
- [初冬函馆的经典游览地](https://www.sakashita.page/hakodate-in-early-winter/)
- [登别温泉:地狱谷、林间步道与泡汤之旅](https://www.sakashita.page/noboribetsu-onsen/)
- [2024年小樽游记:银行街、玻璃店与运河散步](https://www.sakashita.page/otaru-travel-guide-2024/)
## 东北地方
### 青森县
- [青森县立美术馆与三内丸山遗址](https://www.sakashita.page/aomori-art-and-jomon-history/)
- [漫步青森港与睡魔之家Wa Rasse](https://www.sakashita.page/aomori-port-and-nebuta-museum/)
- [青森浅虫温泉与扇贝广场](https://www.sakashita.page/asamushi-onsen-and-hotate-square/)
- [八甲田山旅游指南](https://www.sakashita.page/hakkoda-mountains/)
- [秋天的弘前城](https://www.sakashita.page/hirosaki-castle-in-autumn/)
- [弘前樱花节游记](https://www.sakashita.page/hirosaki-cherry-blossom-festival/)
- [十和田湖周边观光](https://www.sakashita.page/lake-towada-area/)
- [青森中野红叶山的秋日风景](https://www.sakashita.page/nakano-momiji-autumn-leaves/)
- [春日奥入濑溪流:住一晚,沿水走进新绿](https://www.sakashita.page/oirase-gorge-in-early-spring/)
- [大间海鲜之旅](https://www.sakashita.page/oma-seafood-guide/)
### 岩手县
- [平泉中尊寺:走访世界文化遗产](https://www.sakashita.page/chusonji-temple/)
- [花卷温泉与宫泽贤治的童话之乡](https://www.sakashita.page/hanamaki-onsen-and-miyazawa-kenji/)
- [平泉的世界文化遗产](https://www.sakashita.page/hiraizumi-world-heritage/)
### 宫城县
- [松岛一日游:朱桥、古寺与海湾游船](https://www.sakashita.page/matsushima-day-trip/)
- [仙台与伊达政宗](https://www.sakashita.page/sendai-and-date-masamune/)
- [2024年仙台七夕:从历史景点走进纸饰长街](https://www.sakashita.page/sendai-tanabata-festival/)
### 秋田县
- [秋田港与男鹿半岛:寒风山、生剥鬼与海边风景](https://www.sakashita.page/akita-city-guide/)
- [2024年秋田竿灯节游记](https://www.sakashita.page/akita-kanto-festival-2024/)
- [角馆的桦细工:樱树皮里的手工之美](https://www.sakashita.page/cherry-bark-craft/)
- [深秋的角馆:武家宅邸与红叶](https://www.sakashita.page/kakunodate-in-autumn/)
- [秋田田泽湖之旅](https://www.sakashita.page/lake-tazawa/)
### 山形县
- [山形酒田:沿着《阿信》的足迹逛港城](https://www.sakashita.page/sakata-and-oshin/)
- [山形鹤冈经典一日游](https://www.sakashita.page/tsuruoka-day-trip/)
### 福岛县
- [会津若松一日游:博物馆、七日町与乡土料理](https://www.sakashita.page/aizu-wakamatsu-day-trip/)
- [大内宿:走进旧时的驿站街道](https://www.sakashita.page/ouchi-juku/)
- [会津若松鹤城之行](https://www.sakashita.page/tsurugajo-castle/)
## 关东地方
### 栃木县
- [探访日光的世界遗产](https://www.sakashita.page/nikko-world-heritage/)
- [宇都宫近郊:大谷资料馆、多气山与若竹之杜](https://www.sakashita.page/utsunomiya-caves-and-bamboo/)
- [宇都宫一日游:城址、石造教堂与饺子](https://www.sakashita.page/utsunomiya-city-walk/)
### 群马县
- [伊香保的千明仁泉亭与德富芦花](https://www.sakashita.page/ikaho-and-tokutomi-roka/)
- [伊香保温泉与石阶街](https://www.sakashita.page/ikaho-onsen-stone-steps/)
- [草津温泉之旅](https://www.sakashita.page/kusatsu-onsen/)
- [草津温泉的地标“温泉之门”](https://www.sakashita.page/kusatsu-onsen-gate/)
- [世界遗产富冈制丝厂之行](https://www.sakashita.page/tomioka-silk-mill/)
### 埼玉县
- [秩父街头漫步](https://www.sakashita.page/chichibu-town-walk/)
- [小江户川越经典一日游](https://www.sakashita.page/kawagoe-day-trip/)
### 东京都
- [从江户东京博物馆看日本的城郭与住宅](https://www.sakashita.page/edo-tokyo-museum/)
- [东京赏樱散步:五处花景与花期判断](https://www.sakashita.page/tokyo-cherry-blossoms/)
- [东京皇居之行](https://www.sakashita.page/tokyo-imperial-palace/)
- [东京晴空塔与《原神》的特别照明](https://www.sakashita.page/tokyo-skytree-genshin-lighting/)
- [东京晴空塔观光指南](https://www.sakashita.page/tokyo-skytree-guide/)
- [东京站的夜晚](https://www.sakashita.page/tokyo-station-at-night/)
### 神奈川县
- [箱根的经典观光景点](https://www.sakashita.page/hakone-day-trip/)
- [樱花季的镰仓:八幡宫、大佛与湘南海岸](https://www.sakashita.page/kamakura-cherry-blossoms/)
- [紫阳花季的镰仓之旅](https://www.sakashita.page/kamakura-hydrangeas/)
- [小田原经典一日游](https://www.sakashita.page/odawara-day-trip/)
- [镰仓东庆寺之行](https://www.sakashita.page/tokeiji-temple/)
## 中部地方
### 新潟县
秋日的新潟适合慢慢散步。 这里的游记记录了市内漫步与沿途的见闻。
- [秋日新潟:庭园、港口与信浓川的夕阳](https://www.sakashita.page/niigata-city-in-autumn/)
### 山梨县
- [春日富士山:河口湖与新仓山浅间公园](https://www.sakashita.page/mount-fuji-and-cherry-blossoms/)
### 长野县
- [轻井泽散步:旧街、林间教堂与温泉](https://www.sakashita.page/karuizawa-day-trip/)
### 静冈县
- [伊豆城山赏樱之旅](https://www.sakashita.page/izu-shiroyama-cherry-blossoms/)
- [两处河津樱赏花地推荐](https://www.sakashita.page/kawazu-cherry-blossoms/)
- [南伊豆的河津樱与油菜花](https://www.sakashita.page/minami-izu-cherry-blossoms/)
### 爱知县
- [名古屋城之行](https://www.sakashita.page/nagoya-castle/)
## 近畿地方
### 京都府
- [宇治平等院之行](https://www.sakashita.page/byodoin-temple/)
### 大阪府
- [大阪梅田蓝天大厦与空中庭园](https://www.sakashita.page/umeda-sky-building/)
### 兵库县
- [有马温泉经典一日游](https://www.sakashita.page/arima-onsen-day-trip/)
- [有马温泉的节分与变装活动](https://www.sakashita.page/arima-setsubun-festival/)
- [姬路城的历史与参观体验](https://www.sakashita.page/himeji-castle/)
## 中国地方
### 冈山县
- [漫步仓敷美观地区](https://www.sakashita.page/kurashiki-bikan-quarter/)
### 广岛县
- [广岛和平纪念公园:遗址、资料馆与追悼空间](https://www.sakashita.page/hiroshima-peace-memorial-park/)
- [宫岛的etto交流馆](https://www.sakashita.page/miyajima-etto-cultural-center/)
- [宫岛半日游:严岛神社、海岸与商店街](https://www.sakashita.page/miyajima-half-day-trip/)
- [尾道一日散步:山坡、寺院与海边](https://www.sakashita.page/onomichi-day-trip/)
## 四国地方
### 香川县
- [丸龟城与团扇制作](https://www.sakashita.page/marugame-castle-and-fans/)
- [香川高松经典一日游](https://www.sakashita.page/takamatsu-day-trip/)
### 爱媛县
- [道后温泉之旅](https://www.sakashita.page/dogo-onsen/)
- [松山一日游:城郭、街区与道后温泉](https://www.sakashita.page/matsuyama-day-trip/)
### 高知县
- [高知市内一日游:高知城、弘人市场与路面电车](https://www.sakashita.page/kochi-by-tram/)
- [高知五台山与桂滨:MY游巴士一日之旅](https://www.sakashita.page/kochi-godaisan-and-katsurahama/)
## 九州地方
### 福冈县
- [福冈与太宰府天满宫之旅](https://www.sakashita.page/fukuoka-and-dazaifu/)
### 长崎县
- [长崎原爆资料馆与和平公园](https://www.sakashita.page/nagasaki-atomic-bomb-museum/)
- [长崎旅游指南](https://www.sakashita.page/nagasaki-travel-guide/)
### 熊本县
- [熊本城的历史与参观体验](https://www.sakashita.page/kumamoto-castle/)
- [熊本市内一日游](https://www.sakashita.page/kumamoto-day-trip/)
- [熊本的拉面](https://www.sakashita.page/kumamoto-ramen/)
### 大分县
- [别府温泉的地狱巡游](https://www.sakashita.page/beppu-hells/)
### 宫崎县
- [日向神话街道之旅](https://www.sakashita.page/hyuga-mythology-route/)
- [日南海岸自驾:青岛、太阳花园与鹈户神宫](https://www.sakashita.page/nichinan-coast-road-trip/)
- [日南太阳花公园与摩艾石像](https://www.sakashita.page/sun-messe-nichinan/)
### 鹿儿岛县
- [知览一日游:鹿儿岛之行第三日](https://www.sakashita.page/chiran-day-trip/)
- [指宿砂蒸温泉与“指宿玉手箱”列车](https://www.sakashita.page/ibusuki-sand-baths-and-train/)
- [鹿儿岛市内的观光景点](https://www.sakashita.page/kagoshima-city-guide/)
- [樱岛散步:熔岩海岸、火山与黑神鸟居](https://www.sakashita.page/sakurajima-half-day-trip/)
- [鹿儿岛名园:仙岩园](https://www.sakashita.page/senganen-garden/)
### 日本的年号是什么?
URL: https://www.sakashita.page/japanese-era-names/
Last updated: 2026-10-24T17:19:59.000Z
## 什么是年号?
日本除了公历,也使用“年号”(日语称“元号”)纪年。 阅读历史资料、参观博物馆或填写文件时,都可能遇到这种写法。
古时改元的原因不只包括天皇更替,也可能与灾害等事件有关。 明治以后采用“一世一元”的原则,天皇更替时改元。 平成在2019年结束,随后改为令和。
## 近现代年号与公历
| 年号 | 开始的公历年 | 最后一年的纪年 |
| -- | ------ | ------------ |
| 明治 | 1868年 | 明治45年(1912年) |
| 大正 | 1912年 | 大正15年(1926年) |
| 昭和 | 1926年 | 昭和64年(1989年) |
| 平成 | 1989年 | 平成31年(2019年) |
| 令和 | 2019年 | — |
改元可能发生在年中,所以同一个公历年可能对应两个年号。 例如1989年先是昭和64年,之后是平成元年。 “昭和64年”是纪年名称,并不表示昭和持续了完整的64年。
年份换算可参阅[日本法务省的公历与和历对照表](https://www.legal-ab.moj.go.jp/help/HOUMUK/date4-1.htm),历代年号的起始年可查阅[日本国立公文书馆的检索资料](https://www.digital.archives.go.jp/search)。
### 旅行的意义
URL: https://www.sakashita.page/why-we-travel/
Last updated: 2026-10-24T17:09:59.000Z
旅行始于整理行囊,也始于一个念头:离开熟悉的日常,看看别处的人怎样生活。 目的地可以很远,也可以只是邻近的一座小城。 让一段路成为旅行的,往往是我们愿意放慢脚步,认真观察。
## 为自己留一点空白
行程表能帮助我们出发,却不必填满每一个小时。 一条临时拐进去的小巷、一家坐下来吃饭的小店,或是一段什么也不做的车程,都可能成为后来最清楚的记忆。 少去一个景点,有时反而能多认识一点眼前的地方。
## 把见闻带回生活
照片记录风景,文字则能留下当时的心情。 记下为什么喜欢一片海、为什么对某个展品停留许久,比凑齐一张打卡清单更接近旅行的意义。 回到家后,那些片段也许会改变我们看待熟悉事物的方式。
出发不需要一个宏大的理由。 带上必要的行李,给未知留一点位置,就可以开始下一段路。
### 大间海鲜之旅
URL: https://www.sakashita.page/oma-seafood-guide/
Last updated: 2026-10-24T16:59:59.000Z
来到青森,除了苹果,也别忘了下北半岛北端的大间。 这座面向津轻海峡的小镇以金枪鱼闻名,适合把海边散步、港口风景与一顿海鲜料理安排在一起。
## 沿着海边慢慢走

大间海边排列的石柱艺术装置
大间的海岸有开阔的海景,沿途可以看看海边的装置与小店。 即使天气晴朗,海风也可能很强,散步时带件防风外套会更舒服。

宣传大间金枪鱼的彩色布帘
金枪鱼是大间街头反复出现的主题,从布帘、招牌到纪念品,都能看出这座渔港小镇与海的联系。
## 大间崎的纪念像

大间崎的金枪鱼与一本钓手臂纪念像
大间崎是本州最北端,金枪鱼与钓鱼手臂的纪念像是这里具有代表性的拍照地点。 天气好时,可以隔着津轻海峡眺望北海道方向。

写有本州最北端字样的大间崎木标
## 在渔港寻找海鲜滋味

大间室内休息处的金枪鱼形木饰
来大间想吃海鲜,可以在当地餐馆比较金枪鱼刺身、海鲜丼与套餐,再按自己的口味选择。 不必把行程全押在刚捕获的金枪鱼上;当地店家也会保存当季渔获,让游客在不同季节品尝。

停靠在大间港口的渔船
在港口能看到渔船与渔业日常,但卸鱼时间并不固定,参观时也要留出作业空间。 与其赶路,不如在吃饭前后多留一点时间,看看这座小镇真实的生活节奏。

大间海产中心与纪念品店的店面
大间崎、渔港与渡轮码头之间仍有距离,出发前请核对巴士班次与返程时间。 关于餐馆、交通和当地活动,可继续查看观光协会的资料。
[大间町观光协会:町内散步路线oma-wide.net](https://oma-wide.net/model%5Fcourse/)
[大间金枪鱼与旅行常见问题oma-wide.net](https://oma-wide.net/tw/menu/faq/)
### 薰衣草花季相约富良野
URL: https://www.sakashita.page/furano-lavender-fields/
Last updated: 2026-10-24T16:50:00.000Z
富良野的夏天,不只有一片紫色。 薰衣草与其他花卉在坡地上排成不同色带,走到高处,花田后方又接上农田、街镇和远山。
这篇记录富田农场的花季景色,也整理新富良野王子大饭店周边的散步与交通安排。 花况、季节列车和体验活动都会变化,行程最好围绕实际出发日期来排。
## 一张日历带来的转机

从坡地上眺望薰衣草与富良野田园
富田农场最初种植薰衣草,是为了提取香料。 后来香料市场变化,种植逐渐难以维持,许多花田也随之消失。
农场的沿革中,1976年是一个重要转折。 当时,薰衣草田的照片被选入国铁日历,让更多人知道这里,游客也开始陆续到来。 之后,农场将花卉观赏、香包与其他薰衣草产品结合起来,逐渐发展成今天的观光农场。
[富田农场的历史www.farm-tomita.co.jp](https://www.farm-tomita.co.jp/history/)

树林前盛开的薰衣草
## 怎样安排赏花
薰衣草的品种、田块位置与当年的气温,会让开花时间出现差异。 即使同一天到访,也可能看到一部分花田正盛开,另一部分已经收割。

薰衣草与其他花卉交织成的彩色花带
规划花季旅行时,可以先看农场公布的花田介绍与近期照片,再决定想看的区域。 若想拍完整的彩色坡地,站在步道上稍微改变角度,就能得到不同的层次。

富田农场坡地上的多色花田
观赏与拍照都应留在指定步道内。 花田的色带依靠栽种、除草和收割维持,盛开的景色背后也有农场日常工作的节奏。

花田尽头的树林与富田农场步道
## 花田之外的休息时间

花田旁品尝的薰衣草冰淇淋与甜点
逛完花田,可以找地方吃一份薰衣草冰淇淋,再看看香包和其他农场产品。 不同餐饮设施的营业季节并不完全相同,想买某样甜点时,可以先查当天开放的店铺。

农场内停放的拖拉机
这张照片里的拖拉机,也提醒人们这里同时是一座需要管理的农场。 赏花时留意作业车辆的路线,就能避免挡住通道。
[富田农场:花况、设施与交通www.farm-tomita.co.jp](https://www.farm-tomita.co.jp/cn/)
## 怎样前往富田农场
- 🚃 铁路:可先查询JR富良野线至中富良野站的班次,再安排前往农场的步行或接驳。
- 🚗 自驾:花季要预留停车和周边道路拥堵的时间。
- 🚌 巴士:若同时游览美瑛或其他花田,可比较当季观光巴士的停靠点与停留时间。
富田农场位于中富良野町,从富良野站出发仍有一段距离。 安排住宿时,最好同时确认早晨去花田和傍晚回饭店的交通。
曾经很受欢迎的“富良野・美瑛ノロッコ号”已于2026年9月23日迎来最后运行日。 今后的花季旅行,应重新查询JR北海道公布的季节列车与临时站安排。
[JR北海道:富良野与美瑛旅行www.jrhokkaido.co.jp](https://www.jrhokkaido.co.jp/travel/furanobiei/index.html)
## 新富良野王子大饭店周边

新富良野王子大饭店入口旁的向日葵
新富良野王子大饭店位于富良野市的山麓,周边有滑雪场、温泉设施和林间活动空间。 夏季与冬季的体验内容差别很大,住宿时可以按季节选择活动。

园区林间悬挂的彩色雨伞
园区里适合散步的空间,也值得留一点时间慢慢走。 照片中的雨伞装置记录了当时的布置,再次到访时,现场景观可能已有变化。

通往公园高尔夫区域的林间小路
这里的公园高尔夫,与完整的高尔夫球场是不同类型的设施。 选择体验时,先看活动地点、开放日期和所需时间,比较容易与花田行程衔接。

饭店园区内的巴士候车处
如果利用公共交通,应先核对饭店往返富良野站的巴士,再接续前往中富良野或其他景点。 温泉、餐饮与活动是否包含在住宿方案内,也要分别确认。
[新富良野王子大饭店:设施、活动与交通www.princehotels.co.jp](https://www.princehotels.co.jp/shinfurano/)
### 网走一日游:博物馆、流冰与原生花园
URL: https://www.sakashita.page/abashiri-travel-guide/
Last updated: 2026-10-24T16:39:59.000Z
在网走旅行的一天里,我们先骑车参观天都山一带的博物馆,再搭列车前往小清水原生花园。 前半天看的是建筑、历史和北方的生活,后半天则把脚步交给海风与花草。
当时的路线分为两段:
- 🚲 网走站出发,依次参观博物馆网走监狱、北海道立北方民族博物馆、鄂霍次克流冰馆与天都山展望台,再返回车站。
- 🚃 从网走站搭车前往原生花园站,在小清水原生花园散步后返回。
天都山一带有坡路,我们骑到流冰馆时已经有些气喘。 如果想仔细看展,可以把博物馆和原生花园分成两天,不必照搬这次较满的行程。
## 博物馆网走监狱:建筑背后的开拓史

博物馆网走监狱的大门
这里是保存旧网走监狱建筑与相关资料的户外历史博物馆。 移筑的舍房、中央看守所、厅舍等设施,让北海道开拓史不再只是地图上的道路与城镇,也能看到囚犯被迫承担重体力劳动的处境。 参观时,比起把监狱当成猎奇的布景,我更想留意建筑里的生活空间、劳动条件,以及展览如何说明那段历史。

博物馆入口旁的岗亭
馆方记载,旧网走监狱的两组、共八栋移筑建筑于2016年被指定为日本国家重要文化财。 建筑的保存与参观范围可能受到修缮工程影响,出发前可先看[博物馆的展示设施说明](https://www.kangoku.jp/exhibits)。

博物馆内的监狱食堂
监狱食堂也是参观的一部分,提供以监狱餐食为主题的套餐。 它让人从日常饮食的角度接近这个场所,但餐厅体验与真实的服刑生活仍应分开理解。
[博物馆网走监狱www.kangoku.jp](https://www.kangoku.jp/)
## 北方民族博物馆:从器物看不同的生活方式

北海道立北方民族博物馆
北方民族博物馆的视野并不限于北海道,而是把北方地区不同民族的衣食住行放在一起展示。 看服装怎样适应寒冷、工具怎样配合当地材料,再比较不同地区的共通点与差异,比只记住一串地名更容易进入展览。
那次参观,我们借用了语音导览,也体验了馆内的服饰试穿。 这些体验给旅行留下了轻松的片段;理解展品时,仍要回到它所属的地区、民族与生活背景。 导览语言、体验项目与临时展览可查[博物馆官网](https://hoppohm.org/index2.htm)。
## 流冰馆与天都山:夏天也能触到的寒冷
从北方民族博物馆骑向流冰馆时,上坡比地图上的短短一段更有存在感。 抵达后最难忘的是流冰体验室:带进去的湿毛巾很快冻硬,寒冷一下子从一个温度数字变成了身体的感受。

鄂霍次克流冰馆的主题展示
流冰馆以鄂霍次克海的流冰和生物为主题。 馆方介绍的流冰体验空间保持在零下15℃,并展示从海上运来的真实流冰。 水槽里的裸海蝶则像在水中轻轻振动翅膀,让冰冷的主题多了几分轻盈。

天都山的地标与周边导览图
展望台适合用来认识网走周围的地形:海面、湖泊与远方山脉在这里接到同一幅景色里。 能看到多远要看当天的天气,不必把宣传照片里的晴朗视野当作每次都能遇到的保证。

参观后买的海盐牛奶冰淇淋
参观结束后,我们买了海盐牛奶冰淇淋。 淡淡的咸味衬着奶香,是这一天里很具体、也很容易记住的味道。
[鄂霍次克流冰馆的设施介绍www.ryuhyokan.com](https://www.ryuhyokan.com/facility/)
## 小清水原生花园:把速度交给海风
去原生花园之前,我们在车站附近买了便当。 这一段行程不再赶着进入下一座展馆,而是坐车看风景、下车慢慢走。

在网走车站附近买的铁路便当

小清水原生花园的入口标识
小清水原生花园位于鄂霍次克海与涛沸湖之间。 沿步道行走时,花草、海岸与开阔的天空占据了视线,和上午的建筑空间形成了完全不同的节奏。 花期随植物种类与天气变化,出发前先确认当年的开花情况,比追着一个固定的“最佳日期”更实用。

原生花园附近的马群与花丛
原生花园站是季节性营业的车站;小清水町刊载的[JR北海道通知](https://www.town.koshimizu.hokkaido.jp/kouhou/pdf/2416%5F60419505.pdf)说明,车站从2024年起调整了开放期间。 不能只看到地图上有站名,就假定全年、所有列车都能上下车。 去程和回程的停靠站、班次都应一起核对,尤其要先记住最后几班返程车。
## 怎样安排交通

旅途中见到的钏网本线列车
女满别机场是前往网走的航空门户之一,抵达后可继续利用机场巴士进入市区。 铁路方面,长距离抵达网走和沿钏网本线前往原生花园,是两种不同的行程安排;订房与排活动前,要先核对衔接是否合适。

旅行中的网走站
我们那次在市内租车骑行,但天都山的坡度、风势与天气会直接影响体力消耗。 不想骑上坡时,可比较[网走巴士的观光交通](https://www.abashiribus.com/)与当地其他接驳方式。 铁路运行情况和时刻可查看[JR北海道](https://www.jrhokkaido.co.jp/)。
这次旅行最喜欢的,是从展馆走到旷野的变化。 监狱建筑、北方生活的器物、流冰的寒冷,再到原生花园的海风,让网走留下了几种很不一样的触感。
### 登别温泉:地狱谷、林间步道与泡汤之旅
URL: https://www.sakashita.page/noboribetsu-onsen/
Last updated: 2026-10-24T16:29:59.000Z
登别的风景,常从温泉蒸汽开始。 地狱谷的岩坡、大汤沼的热水、林间的溪流,与温泉街上的鬼像彼此呼应,让泡汤之外的散步也很有内容。
以前来登别时,我们住过第一泷本馆和汤本登别。 这篇把住宿、温泉街与周边的自然景点放在一起,方便按停留时间挑选。
## 从车站到温泉街,还需要一段交通

JR登别站的售票与检票区域

JR登别站内展示的棕熊标本
JR登别站和登别温泉街分属两个地点。 从札幌乘铁路到登别后,还需要转巴士或出租车前往温泉街。 从新千岁机场出发,可以比较直达巴士与铁路换乘,按航班抵达时间选择。

登别温泉街的低速电动观光巴士
温泉街还有低速电动观光巴士,运行日期、时段与路线可能调整。 带着行李或准备去地狱谷时,可以先查[巴士的运行说明](https://oni-slo.com/),同时确认当天是否有临时变更。
[登别官方旅游信息与交通noboribetsu-spa.jp](https://noboribetsu-spa.jp/)
## 住过的两家温泉酒店
### 第一泷本馆

登别第一泷本馆的酒店建筑
第一泷本馆的历史始于1858年,与泷本金藏一家开发温泉的故事相连。 酒店靠近地狱谷,大浴场是这里很有代表性的空间。
馆方介绍有五种泉质,可以按温度、浴槽和景色选择。 高温源泉在部分浴槽中需要加水调温,具体处理方式应看浴场的分析与利用说明。

第一泷本馆内指定的饮泉设施
馆内另有指定的饮泉设施,饮用范围和方法有专门说明。 照片所示的设施有自己的管理条件,普通浴槽中的水不用于饮用。

第一泷本馆大厅中的金色巨棒装置

到访时第一泷本馆内的地狱太鼓表演
大厅里的巨大金棒和到访时看到的太鼓表演,也让酒店带着鲜明的登别气息。 表演、泳池与其他馆内项目有各自的开放安排,预订时可以一起确认。
[第一泷本馆:浴场与住宿takimotokan.co.jp](https://takimotokan.co.jp/ja/spa/)
### 汤本登别

汤本登别温泉酒店的正面入口
汤本登别靠近温泉街的巴士总站,适合把乘车与住宿衔接起来。 馆内引入三类源泉与一类混合泉,能在同一处浴场比较不同的汤色和感受。
选择房型时,把餐食方式、浴场和交通一起看,比单看星级更容易找到适合自己的住宿。 我们曾多次入住这两家酒店,照片与感受属于当时的旅行记录。
[汤本登别官方网站yumoto-noboribetu.com](https://yumoto-noboribetu.com/)
## 地狱谷:从木栈道看火山地形

斜阳下登别地狱谷升腾的蒸汽
地狱谷位于支笏洞爷国立公园,是登别很有代表性的火山景观。 裸露的岩壁、地面颜色和不断升起的蒸汽,让地热活动变得直观。

登别地狱谷观景步道旁的导览图
沿开放的观景步道走,可以从不同位置看谷地。 热水、喷气口和松软地表都有各自的危险,观察时留在指定范围内,按现场指示行动。
地狱谷的短程观景,和继续走到大汤沼、天然足汤,是不同长度的安排。 冬季积雪、天气与设施维护也可能改变通行范围。
[登别地狱谷参观信息noboribetsu-spa.jp](https://noboribetsu-spa.jp/spot/spot0034/)
## 大汤沼与林间的天然足汤

群山环绕、泛着热气的大汤沼
大汤沼的热水与蒸汽,让这里呈现出与普通湖泊不同的景色。 它是观赏地热活动的地点,水面与深处的温度也有差异,参观时不要进入池内。

林间通往大汤沼、大正地狱与天然足汤的路标
从开放步道继续走,可以到大汤沼川一带。 岔路处先看导览牌,确认天然足汤的入口,以及当前是否允许通行。

大汤沼河
天然足汤利用流经林间的温泉水。 水温和水量会变化,能否使用应按当天现场说明判断;路面湿滑或有封闭提示时,就改走其他路线。
当前步道维护与临时关闭,请查看[登别观光协会公告](https://noboribetsu-spa.jp/news/)。
## 俱多乐湖:另留一段移动时间

俱多乐湖畔的木质湖名标识
俱多乐湖位于白老町,是被森林环绕的火山口湖。 它和地狱谷一带距离不远,但沿途道路和交通条件不同,不能只凭地图上的直线距离安排步行。

晴朗天气下平静的俱多乐湖湖面
湖面平静时,山的轮廓与天空映在水中,气氛和冒着蒸汽的谷地很不一样。 如果想把它加进温泉之旅,先确认道路季节性封闭与实际交通方式。

俱多乐湖畔的木造休息与划船设施建筑
湖畔活动有各自的经营和管理范围。 照片里看到的设施,不代表任何季节都能租船或垂钓,出发前应确认当前接待项目。
## 温泉街与熊牧场

旅行途中在草地上偶遇的鹿
回程路上遇到的鹿,也成了这一天的小插曲。 在野外看见动物时,远远观察就好,不必靠近或投喂。

登别熊牧场的索道乘车入口
登别熊牧场从温泉街乘索道上山。 它是独立的参观项目,索道运行、入园及展望台开放请查[牧场官方网站](https://bearpark.jp/)。 想同时去湖边和牧场,就需要留出更充足的时间。
## 泉源公园:夜间的蒸汽

夜间泉源公园喷发的间歇泉蒸汽

泉源公园的间歇泉
泉源公园就在温泉街上,间歇泉喷发时,蒸汽会突然充满眼前的空间。 晚上散步时经过这里,灯光下的热气又有另一种气氛。
不必为了精确追一轮喷发而把其他安排卡得太紧。 沿街吃点东西、看一看鬼像,再回酒店休息,也很适合登别。
## 温泉街上的鬼与阎罗王

登别街头的鬼像与欢迎木牌

汤泽神社附近的鬼祠与两侧鬼像

泉源公园的鬼塑像

日归浴场门前的汤鬼藏石像
这里的鬼有的威严,有的带着笑脸,还会出现在纪念品和街道装饰上。 沿路找它们,是温泉街散步里很轻松的一部分。

阎罗王堂内的阎罗王机关像
极乐通商店街的阎罗王堂设有机关表演,表情和音效会发生变化。 演出时段及临时停演请以当天公告为准,经过时先看一下说明即可。
## 一晚怎样安排
- 🚌抵达后:从JR登别站或机场转车到温泉街,放下行李。
- 🚶下午:先走地狱谷,再按步道开放与体力决定是否去大汤沼。
- 晚上:吃饭、泡汤,在温泉街短程散步。
- 次日:选择俱多乐湖或熊牧场等一处,再配合返程交通。
登别的温泉与地热景观值得分开慢慢体验。 留一晚,也让酒店里的休息成为旅行本身的一部分。
### 2024年小樽游记:银行街、玻璃店与运河散步
URL: https://www.sakashita.page/otaru-travel-guide-2024/
Last updated: 2026-10-24T16:19:59.000Z
2024年这次到小樽,我们沿着银行街、堺町通和运河散步,也把市场与玻璃店放进行程。 小樽的景点看起来集中,认真逛起来却很容易超过一天。 先分清想看建筑、购物,还是去郊外,会更容易安排。
## 从札幌到小樽,再决定怎样走

JR小樽车站
从札幌可乘JR列车或巴士前往小樽。 抵达JR小樽站后,银行街与运河方向适合步行,想去祝津等较远区域,则需要另查公交。
### 自行车与巴士

小樽站附近的自行车租赁与行李寄放店
小樽有不少坡路。 天气和路况合适时,自行车能扩大活动范围,但冬季、结冰或大雨时应改用其他交通。 [站前自行车店的租赁说明](https://kitarin.info/)可用来确认当前营业与借还条件。

JR小樽站附近的中央巴士总站
中央巴士总站就在车站附近。 前往水族馆、祝津或其他郊区时,先核对路线与返程末班,再决定停留多久。
[中央巴士:小樽地区路线www.chuo-bus.co.jp](https://www.chuo-bus.co.jp/city%5Froute/course/otaru/)
## 银行街:从金融建筑走进美术馆
色内一带曾因港口贸易和金融活动而繁荣。 今天仍保留着多座银行建筑,其中一些成为美术馆或资料馆,也有只适合从外部欣赏的建筑。
### 似鸟美术馆

使用原北海道拓殖银行小樽支店建筑的似鸟美术馆
似鸟美术馆沿用了原北海道拓殖银行小樽支店。 作家小林多喜二曾在这里工作,银行建筑的圆柱与挑高大厅也保留了鲜明的时代感。
馆内介绍日本画、油画、木雕和彩色玻璃等艺术。 不同作品的展出安排会变化,先看当前展览,再决定要停留多久。
### 旧安田银行小樽支店

旧安田银行小樽支店的石造立面与圆柱
旧安田银行的立面适合从街边细看。 圆柱、窗框与石材的比例,让散步变成了一段近代建筑观察。
### 旧三井银行小樽支店

旧三井银行小樽支店与旁边的彩色装置
旧三井银行小樽支店建于1927年,2022年成为日本国家重要文化财。 它也是小樽艺术村的一部分,室内可看银行营业空间、金库及建筑细部。
似鸟美术馆、旧三井银行等设施各有内容,票券与组合方式请查[小樽艺术村官方网站](https://www.nitorihd.co.jp/otaru-art-base/)。
## 堺町通:玻璃店与甜点
### 大正硝子馆

大正玻璃馆所在的历史建筑

小樽大正硝子馆的店前招牌
堺町通有不少玻璃制品店。 大正硝子馆一带既能看历史建筑,也能逛器物、小饰品和工艺商品。 如果计划参加制作体验,需要先确认店铺、时段与预约要求。
### 北一硝子与街边的老建筑

以花朵图案拼接的彩色玻璃灯
玻璃灯的颜色与光线很有吸引力。 从远处看是灯罩的花纹,走近后又能观察拼接、色彩和表面的反光。

堺町通沿街的历史建筑与商店
北一硝子三号馆使用了明治时代的木骨石造仓库,1983年作为店铺开放。 小樽的旧仓库在今天继续被使用,也是这条街耐看的原因之一。

北一硝子陈列的彩色玻璃油灯
店里的油灯和日常器皿各有不同的用途。 挑选时可以多比较重量、握持感和实际使用方式,喜欢的东西也未必需要立刻决定。
### 多层冰淇淋与玻璃体验

北一硝子附近品尝的多层冰淇淋

店门口介绍八层冰淇淋的海报
这次在北一硝子附近吃到的多层冰淇淋,把好几种味道叠在一起,很适合当作逛街途中的小休息。 照片里的口味、价格和层数属于当时的商品,来访时按店内菜单选择即可。
北一硝子的体验工房提供喷砂加工和玻璃珠饰品组合等项目。 后者是挑选现有珠子与配件制作小物,和亲自熔制玻璃珠是不同的体验。
[北一硝子体验工房的项目与预约kitaichiglass.co.jp](https://kitaichiglass.co.jp/seisakutaikenkobo/)
## 童话十字路口与音乐盒堂

小樽音乐盒堂前的蒸汽时钟
音乐盒堂本馆前的蒸汽时钟,是堺町通一带的地标。 时钟本身以电力运行,并用锅炉产生蒸汽,让上方的汽笛按时奏出旋律。

童话十字路口附近的建筑与街头装置
逛完商店后,可以在这里听一段声音,再决定继续去海岸,还是前往南小樽站方向。 [音乐盒堂的时钟介绍](https://www.otaru-orgel.co.jp/steam-clock)也说明了它与加拿大制钟师的关系。
## 小樽运河:在水边读城市历史

小樽运河边爬满绿藤的旧仓库
小樽运河于1923年建成,曾服务于港口的货物转运。 后来经历保存与填埋的争论,部分河段缩窄,并整备了今天的道路与散步空间。
沿岸的仓库、石路和灯具,构成了人们熟悉的小樽景色。 从同一个地点在不同时间回望,光线与人流都会改变画面的气氛。

停泊在小樽运河上的游览船
运河游船从水面经过仓库和桥梁,让散步多一个角度。 具体航程、票价与天气停航,请查[小樽运河游船官方网站](https://otaru.cc/)。
## 北运河:旧日本邮船支店与公园

旧日本邮船小樽支店的历史建筑
旧日本邮船小樽支店于1906年建成,1969年成为日本国家重要文化财。 2024年到访时仍处于保存修缮期间;该建筑已于2025年4月25日重新开放。
准备入内时,可以查看[小樽市的建筑与参观信息](https://www.city.otaru.lg.jp/docs/2022011900078/)。 它位于较北侧,和堺町通之间有一段距离,适合连同北运河一起安排。

石壁仓库内的船形玩具设备
运河公园附近也保留着仓库空间。 照片里的船形游具,让港口历史以很直观的方式进入孩子的活动之中。 当前是否开放,请以现场管理说明为准。
## 手宫线遗址与综合博物馆

手宫线铁路遗址的铁轨与枕木
手宫至札幌之间的铁路于1880年开通,是北海道最早的铁路。 手宫线在1962年结束旅客业务,货运则持续到1985年。 如今部分旧线成为步行空间,可以沿铁轨感受它与街区的关系。
### 小樽市综合博物馆本馆

小樽市综合博物馆园区的木造旧站房

北海道铁道的发源地

小樽市综合博物馆本馆的入口
博物馆位于旧手宫铁路设施一带,通过车辆、站房和资料介绍北海道的交通历史。 蒸汽机车“铁马号”有季节性体验运行,也可能因检修临时停运。
如果对铁路感兴趣,就把这里列为半天的重点。 只在闭馆前匆匆走一圈,很容易错过户外展示与活动。
## 小樽吃什么
### 三角市场

小樽车站附近市场
三角市场靠近JR小樽站,适合与到达或离开车站的时间衔接。 店铺空间不大,海鲜摊和餐厅挨在一起,先看当日菜单和等候情况,再决定在哪里吃。

三角市场店内展示的大螃蟹

出售的各种盒装海鲜
海鲜种类与价格随季节、进货和店铺而变。 吃海鲜盖饭时,也可以按自己喜欢的食材搭配,不必只追着图片里最豪华的一碗。
[小樽三角市场otaru-sankaku.com](http://otaru-sankaku.com/)
### 寿司店与出拔小路

小樽街头的寿司店与餐饮招牌
市中心有寿司店,也有不同价位与形式的海鲜餐厅。 菜单语言、预约和营业时间因店而异,选店时和当天路线一起考虑。

小樽出拔小路的塔楼与餐饮招牌
浅草桥附近的出拔小路集中着小型餐饮店,塔楼与招牌很醒目。 在运河边散步后,到这里找一点吃的,也是很顺路的一段。
## 祝津的旧青山别邸

小樽贵宾馆旧青山别邸入口一带的庭园

小樽贵宾馆的入口门柱
旧青山别邸与当地鲱鱼产业的发展相连,建筑、庭园和室内装饰值得细看。 它位于祝津,和市中心的银行街、堺町通分属不同区域,需要另外安排交通。
想去的话,把往返巴士和参观留成完整的一段。 [小樽贵宾馆官方网站](https://www.otaru-kihinkan.jp/)提供当前参观和餐饮信息。
## 田中酒造:本店与龟甲藏要分开找
田中酒造本店位于色内,提供商品销售等服务。 想看酿造现场,则应前往信香町的龟甲藏,从南小樽站方向安排更方便。
工厂以玻璃窗后的展示介绍酿造过程,实际作业会随当天发酵与生产安排改变。 驾车、骑自行车及未满20岁的来访者不能参加试饮。
[田中酒造:龟甲藏参观说明tanakashuzo.com](https://tanakashuzo.com/visit/)
## 一天怎样取舍
- 🚶街区路线:小樽站、银行街、堺町通和运河,途中安排午餐与购物。
- 🏛️建筑与铁路:减少商店停留,把北运河、旧日本邮船支店和综合博物馆作为重点。
- 🚌郊外路线:若想去祝津,就留出完整往返时间,减少市中心的参观项目。
照片记录了2024年的到访,但小樽并不需要一次逛完。 先选想认真看的两三处,再沿路加入一顿饭和一段散步,更容易留下清楚的记忆。
### 神威岬与余市的自驾之旅
URL: https://www.sakashita.page/cape-kamui-and-yoichi/
Last updated: 2026-10-24T16:09:59.000Z
这次租车从小樽出发,先去神威岬,再回余市参观Nikka蒸馏所。 海角的步道与酒厂的建筑,是这一天两种很不一样的风景。
自驾行程里,蒸馏所的参观时段需要事先安排。 负责开车的人全程不饮酒,也可以通过展示、建筑和无酒精饮品参与这段参观。
## 229号国道旁的小狐狸
从小樽出发,我们沿5号国道,再转入229号国道。 离开积丹的街区后,路边出现了一只金色的小狐狸。

229号国道旁偶遇的狐狸
它看起来没什么精神,我们很惊喜,但没有投喂,也没有追过去。 看着它消失在草丛里,便继续前行。 这段短暂的偶遇,成了旅途中很清楚的一幕。
## 神威岬的步道

神威岬停车场旁的入口区域

步道途中的休息广场
从停车区域到海角先端,需要走一段有起伏的步道。 海风、雨水与步道状况都会影响通行,入口大门和前方步道也有各自的关闭安排。
观光协会以从“女人禁制之门”到先端单程约20分钟作为参考。 加上从停车场走到门口、拍照和休息,最好留出更宽裕的往返时间。
### 地名与传说

保留历史称呼的“女人禁制之门”
这里流传着与义经和当地女性有关的悲恋传说:女子追随离去的爱人来到海角,最终化为神威岩。 这属于当地叙事,不能据此认定义经确实逃到了北海道。
“女人禁制”是历史称呼,如今不按这个性别规定限制参观。 今天是否能继续前行,要看天气、设施开放与现场通行指示。
[积丹观光协会:神威岬与步道www.kanko-shakotan.jp](https://www.kanko-shakotan.jp/spot/%E7%A5%9E%E5%A8%81%E5%B2%AC/)
## 灯塔与海角先端

神威岬灯塔

步道旁的旧观测设施遗迹

神威岬植被间的蝴蝶
沿途除了大海,也能看到灯塔、旧设施与低矮植被。 风大的时候,更能感受到步道两侧都朝海展开的地形。

矗立在海中的神威岩

神威岬步道与海面
走到先端,海水的蓝色从岸边向外逐渐变深,神威岩立在水中。 这趟旅行想看的开阔景色,也在这里展开了。

返回途中可以坐下休息的长椅
回程仍需要走同一段有起伏的路。 拍完照后留一点时间休息,再回到停车区域,不必等接近闭门才折返。
## 在岬口看展示与礼品

神威岬礼品设施的入口

印有神威岬字样的纪念T恤

通往楼上展示空间的阶梯

礼品店里的当地商品
岬口的“KAMUI番屋”有礼品与介绍海角的展示。 我们在这里稍作停留,再沿海岸往余市方向返回。

展示空间内的灯塔透镜
照片里这件大型器物,是灯塔的透镜装置。 从步道上的灯塔走到这件透镜前,海角也多了一段与航路、海上交通有关的故事。
## 余市Nikka蒸馏所

余市蒸馏所的入口与建筑

石砌厂房与酒桶展示
竹鹤政孝在余市建立蒸馏所,这里的气候、建筑与设备,构成认识Nikka威士忌的一部分。 参观时可以先看制造流程,再把历史人物与实际厂房联系起来。

厂区内排列的铜制蒸馏器

竹鹤夫妇旧宅相关的建筑展示
导览参观需要提前预约,博物馆、商店等区域则有不同的入口与利用方式。 想参加导览时,先确定名额与集合时间,再反推从神威岬离开的时刻。

参观后的试饮区域

Nikka的产品与品牌展示
试饮条件与可提供的饮品,应看当天说明。 开车、骑车及未达饮酒年龄的同行者,应选择不含酒精的安排。 购物时也不必急着决定,在自己了解过的风味里挑选即可。
[余市蒸馏所:预约与参观区域nikka.com](https://nikka.com/distilleries/yoichi/visit/)
## 自驾时先留出哪些余量
神威岬是否通行,可能直到当天才因海风和天气改变。 出发前看公告,必要时调整海角停留时间,并遵守现场闭门安排。
乡间道路上的动物、雨雾和季节变化,也会影响行车。 先安排补给、停车与休息,再把蒸馏所预约放在能够从容抵达的时段。 这条路线最适合留一点弹性,给眼前的海色与偶遇留下时间。
### 2024年秋田竿灯节游记
URL: https://www.sakashita.page/akita-kanto-festival-2024/
Last updated: 2026-10-24T15:59:59.000Z
2024年8月,我和朋友去了秋田竿灯节。 白天的妙技大会让人看清撑竿者怎样寻找平衡,夜里成排的灯火又把同一种技艺变成了盛夏街头的景象。 这篇把那天的参观、午餐与夜间观看连在一起,保留当时的照片。

秋田竿灯节的拍照看板与竿灯展示
## 先认识竿灯,再去看比赛
秋田竿灯的背景与驱除睡意、祈求五谷丰登等民俗有关。 竹竿上悬挂的提灯常被比作稻穗,不是麦穗。 历史沿革与技法,可见[竿灯节官方介绍](https://www.kantou.gr.jp/about/);今天的祭典也不是从某一年起完全不变地延续下来的单一形式。

旅行时使用的秋田市中心循环巴士
当天在市内移动参考了循环巴士,最新路线与票券请看[秋田市的巴士说明](https://www.city.akita.lg.jp/kurashi/kotsu/1012207/1007651.html)。 节日期间的交通与平常不同,不宜把旧票价和单圈时间当成随时可用的保证。
这次大致的顺序是:
1. 🚶 先看民俗艺能传承馆与旧金子家住宅。
2. 到市区会场看白天的竿灯表演,中午在秋田站附近用餐。
3. 下午参观红砖乡土馆,再买些点心。
4. 入夜后到竿灯大道看正式演出。
## 民俗艺能传承馆:近看提灯与技艺

秋田市民俗艺能传承馆
在馆里,工作人员介绍了提灯上的队伍标志,以及竿灯的几种尺寸。 最大的大若竿灯,基本规格长约12米、重约50公斤,悬挂46盏提灯。 中若、小若与幼若则有不同规格,不能把体验用的小竿灯数量与正式规格混为一谈。

民俗艺能传承馆内挂满各队标志的提灯
较小的竿灯让人容易接近这项技艺,真正试着支撑时,才会注意到手掌与竹竿之间细小的移动。 我们也穿上祭典服装“法被”拍照,给上午留下了轻松的一段。

拍照时可以穿着的日式祭典服装“法被”

民俗艺能传承馆内可体验的和太鼓
楼上的太鼓体验,把视觉之外的节奏也带了进来。 参观与体验项目的开放安排,应看[传承馆当前公告](https://www.city.akita.lg.jp/kanko/kanrenshisetsu/1003644/index.html)。
## 旧金子家住宅:商铺与生活在同一处

金子家当时的店面
旧金子家住宅与传承馆相邻。 店面、生活空间与仓库相接,让人可以从买卖的场所继续看到一家人的日常,而不只是看一间孤立的旧商店。

金子家存放物品的土藏
从店面走向后方,再看灶与仓储空间的位置,建筑怎样支持经营与生活便更清楚了。 参观时不必把每件旧物都记下来,先看空间之间的关系就很有意思。
## 白天的妙技大会

昼竿灯妙技大会会场前的提灯

白天的竿灯妙技大会现场
白天看比赛,撑竿者的姿势特别清楚。 手掌、额头、肩与腰轮流成为支点,竹竿随风弯曲,人也不断微调。 掌声常在竿灯稳定下来的那一刻响起,紧张感并不需要等到天黑才出现。

千秋公园盛开的荷花
会场附近的千秋公园,荷花也正好盛开。 从人群走到水边看看,给这一天换了一段安静的节奏。
## 夏天的一口冰淇淋

街边售卖手铲冰淇淋的摊位

粉红与黄色花瓣造型的手铲冰淇淋
街边的手铲冰淇淋,粉色与黄色一层层叠成花瓣。 看摊主用小铲把形状做出来,再在暑气里吃上一口,本身就是这次祭典的记忆。

观众体验用手撑起竿灯

JR秋田站附近会场的竿灯表演

参赛队伍的提灯与队名牌
当时也有面向观众的竿灯体验。 现在再去,需要按会场、时间与工作人员的安排参加,不是随时可以走进表演区域。
## 午餐:秋田站附近的乡土料理

乡土料理店KAMAKURA的锅物料理

涂有味噌的烤米棒料理

午餐中的米棒锅、比内地鸡烤串与稻庭乌冬面
中午我们在秋田站附近的KAMAKURA吃饭。 米棒锅、烤物与乌冬面把上午的消耗补了回来,夏天吃热锅也是那次旅行的一点特别之处。 菜单与营业时间会变化,这里记录当时吃到的料理,不作为今天仍有同一套餐的承诺。
## 红砖乡土馆:从街头回到馆内

树木掩映下的秋田市立红砖乡土馆

红砖乡土馆的砖墙与转角建筑
红砖乡土馆保存旧秋田银行总行本馆等空间,也介绍当地美术与工艺。 建筑本身与室内展品,是两种可以同时观察的内容。

红砖乡土馆内的胜平得之纪念室
馆内有版画家胜平得之与金工家关谷四郎的相关展示。 最新展览与开放时间见[红砖乡土馆官网](https://www.city.akita.lg.jp/kanko/kanrenshisetsu/1003617/index.html),不是传承馆的另一个入口。
## 买些点心,再等夜色下来

秋田菓子宗家薰堂的店面

在薰堂购买的点心与彩色糖果
在薰堂,我们买了小馒头与彩色糖果。 糖果像一颗颗小宝石,旅行结束后送给朋友,也很受欢迎。 味道与收到时的心情,让这些小点心也成了旅行回忆的一部分。
## 夜里的竿灯大道

傍晚入场的竿灯队伍
2024年的夜间演出在8月3日至6日举行。 入夜以后,大量提灯在竹竿上亮起,从远处看像一片发光的稻穗。 白天注意的是怎样撑稳,夜里则更容易被灯火、太鼓、笛声与有节奏的助威声一起包围。

秋田夜空中点亮的竿灯
撑竿者调整身体,提灯随之轻轻晃动,观众的掌声又在下一次稳定时响起。 这段反复不是单调的重复,而是那晚最吸引人的地方。
以后再访,观览席、免费观看区域与交通管制应查[竿灯节当年的官方安排](https://www.kantou.gr.jp/)。 照片留下的是2024年的那个夏夜,新的行程则需要新的时间表。
### 2024年仙台七夕:从历史景点走进纸饰长街
URL: https://www.sakashita.page/sendai-tanabata-festival/
Last updated: 2026-10-24T15:49:59.000Z
2024年8月7日,我和朋友们来到仙台,体验这里的七夕节。 白天走伊达政宗相关的历史景点,傍晚进入商店街,头顶的长饰随风摇动,城市的节奏也跟着变了。
仙台七夕通常在8月6日至8日举行。 来年的配套活动、交通管制和展示地图,需要另外查看当年的公告。

仙台站的正面站房与站前平台
## 先把白天与夜晚分开安排

仙台站内装饰七夕彩饰的楼梯
从东京前往仙台,可以乘东北新干线。 从仙台机场入城,则搭乘机场铁路,再换市内交通。
这次朋友是第一次来仙台,我们把城址、瑞凤殿和大崎八幡宫安排在白天,傍晚回到市中心看七夕装饰。 观光巴士Loople可以衔接部分历史景点,但它有自己的末班时间,夜晚逛街的返程要另作安排。

仙台Loople观光巴士的复古车身
[Loople仙台:路线与乘车信息loople-sendai.jp](https://loople-sendai.jp/)
## 七夕纸饰里的愿望

竹枝上系满心愿短笺的七夕装饰
把愿望写在短笺上、系到竹枝,是许多人熟悉的日本七夕习俗。 仙台七夕最醒目的则是大幅垂下的纸饰,常把五根吹流组成一套,挂在竹竿上展示。
传统的“七种装饰”包括短笺、纸衣、纸鹤、钱袋、投网、纸屑篓和吹流。 它们分别承载学业、手艺、健康、丰收与节俭等愿望。 知道这些寓意后,再看纸饰的形状,就能发现豪华外观里的细节。
[仙台七夕的传统装饰与寓意www.sendaitanabata.com](https://www.sendaitanabata.com/about/decoration/)
## 白天:仙台城址、瑞凤殿与大崎八幡宫
### 从仙台城址俯瞰城市

伊达政宗骑马像
城址位于青叶山,伊达政宗骑马像是这里的地标。 天气通透时,可以俯瞰广濑川与仙台市区。
中午,我们在本丸会馆吃了牛舌,也尝了毛豆点心和鱼糕。 先吃好饭,再继续下午的参观,比一路追赶景点舒服许多。
### 瑞凤殿的黑漆与金饰

瑞凤殿华丽的金色雕饰与黑色门扉
瑞凤殿是伊达政宗的灵庙,初建于1637年。 原建筑在1945年战火中烧毁,今天看到的瑞凤殿于1979年重建,延续了原有的桃山式装饰。
同一片墓所还有第二代藩主忠宗的感仙殿、第三代藩主纲宗的善应殿。 参道、雕饰与树木值得慢慢看,七夕期间的布置又添了一层节日气氛。
[瑞凤殿:建筑与历史www.zuihoden.com](https://www.zuihoden.com/about/)
### 大崎八幡宫

大崎八幡宫鸟居与七夕期间的装饰
大崎八幡宫的现有社殿由伊达政宗于1607年造营。 我们到访时,参道和鸟居旁也挂着七夕装饰,历史景点与城市里的节庆自然接在了一起。
## 傍晚:走进商店街
傍晚回到市中心后,我们沿商店街散步。 名挂丁、Clis Road和Marble Road等街区彼此相连,可以从仙台站向西逐段走,不必每走一段就折返。

仙台商店街悬挂的彩色七夕长饰
长饰垂到行人头顶附近,纸花、折鹤与细长流苏层层展开。 同一套装饰从远处看是色彩,走近看则是许多细小的手工。

2024年到访时展示获奖标识的七夕彩饰

标有优秀奖的彩色七夕长饰
各商店街会评选当年的装饰,奖项与评选方式以对应主办方的说明为准。 我们沿途看见获奖标识,也会停下来比较它们的配色与造型。

夜晚亮起站名灯光的仙台站

挂满七夕彩饰的名挂丁商店街入口

Marble Road商店街入口的七夕长饰
## 勾当台公园的夜间热闹

2024年七夕期间市中心的小吃与饮品会场

勾当台公园夜间热闹的小吃摊位
到勾当台公园一带,灯光、小吃摊和人声让夜晚热闹起来。 白天看建筑,晚上在人群里慢慢走,是这一天很鲜明的转换。

到访时会场内写心愿短笺的体验区
现场也有写短笺的空间。 不只是仰头看别人制作的装饰,自己写下几句话,也让这趟节庆旅行留下了参与感。
### 遇到雨时
突然下雨时,店家会赶紧为没有拱廊保护的纸饰套上透明罩。 看到他们抢着遮雨,才更直观地感受到这些手工作品需要怎样被照顾。

仙台拱廊商店街的竹架与七夕纸饰
## 学生们折出的纸鹤

由当地学生制作、寄托复兴心愿的大型纸鹤装饰
最让人停下脚步的,是当地学生共同制作的大型纸鹤装饰。 密密的折鹤从高处垂下,每一只都很小,组合起来却让整个空间有了重量。
这类作品延续着2011年东日本大地震之后对复兴与未来的祈愿。 走到它下面时,比起数清有多少纸鹤,更让人记住的是许多人一起完成一件事的心意。
## 商场里的另一种七夕

仙台三越内展示的七夕彩饰
商场里也能遇见装饰,让节庆延伸到日常购物的空间。 我们最后走到三越一带,再回车站,结束这一天。
## 再访时怎样安排
- 🚌白天:选两三处历史景点,按观光巴士的运行时间衔接。
- 🚶傍晚:从仙台站进入商店街,边走边看七夕装饰。
- 晚上:视当年会场安排加入公园活动,再用铁路或普通市内交通返程。
夏天在户外走得久,午后留一点室内休息时间,夜晚更有精神慢慢逛。 照片里的作品和摊位属于2024年的到访记录,新一年的地图、展示与配套活动请查看[仙台七夕官方网站](https://www.sendaitanabata.com/)。
### 日本JR周游券与铁路旅行入门
URL: https://www.sakashita.page/japan-rail-passes/
Last updated: 2026-10-24T15:39:59.000Z
JR周游券是否划算,取决于行程,而不是每天坐得越远越好。 先选想停留的城市,再比较单买车票、全国版周游券与地区版票券,才能判断钱花在了哪里。 不必为了“回本”把旅行变成连续换车。

停靠在车站的绿色新干线列车
## 先分清车票上的两种费用
购买JR车票时,乘车券对应基本运费;乘新干线或特急列车,通常还需支付特急费用。 普通车、绿色车厢、自由席与指定席,又关系到不同的设备和座位条件。 票面有时合并打印,不能只看自己手里有几张纸,判断是否买齐所需票券。
交通IC卡也不等于可以直接搭乘所有新干线和特急。 是否需要另购特急券、预约或使用专门服务,应按具体线路确认。

日本列车内整齐排列的座椅
## 自由席、指定席与绿色车厢
指定席表示预先确定车次与座位,自由席则在相应车厢寻找空位。 部分列车实行全车指定席,繁忙期间的安排也可能变化,不能假设每列车都有自由席可选。
绿色车厢是一种较高等级的车厢,购买普通车用周游券并不代表可以直接坐进去。 需要追加哪些费用,应看对应票券的车辆适用范围。
## 带孩子时,别把所有费用都算成半价
JR通常把6至未满12岁划为儿童,12岁以上划为成人,但日本小学就读情形另有例外。 儿童的乘车券、特急券等通常按成人半价计算,绿色车厢、GranClass和卧铺等费用则与成人同额。
幼儿与婴儿通常免费,但单独使用指定席、卧铺,以及同行人数等情形会影响是否需要儿童票。 年龄区分与例外请直接核对[JR东日本的儿童票规则](https://www.jreast.co.jp/kippu/06.html),再按所用列车购票。

停靠在站台的日本特急列车
## 全国版与地区版,分别比较
全国版JAPAN RAIL PASS由JR集团提供,地区版则各有发行公司、区域、天数、资格与限制。 “某一张券可以坐”不代表另一张名字相似的券也可以。 选购时至少把以下事项放在一起看:
- 计划中的每一段是否在适用范围内。
- 有效天数是否连续,以及哪一天开始使用。
- 所搭列车与车厢是否包含在内。
- 需要预约的班次是否还有合适座位。
- 不买周游券时,同一行程的总价是多少。
全国版乘坐“希望号”与“瑞穗号”另有专用追加票制度,不是持券后可以直接上车。 具体区间与条件见[官方追加票说明](https://japanrailpass.net/use/special-ticket/)。
## 资格与购买方式
全国版对外国旅客的主要条件,是以旅游目的、按“短期滞在”资格入境,其他适用情形也各有证明要求。 “在日本只住几天”不是这一入境资格的同义词。 购买前应查[官方资格说明](https://japanrailpass.net/about%5Fjrp/riyou/),不要等到领票时才确认。
购买可以经官方网站,或按规定通过海外代理渠道办理,两者的取票、预约与材料安排并不完全一样。 [官方购买流程](https://japanrailpass.net/purchase/)把这两条途径分开介绍。 使用自动化入境通道时,也留意是否需要另外取得用于确认资格的证明。
## 启用日:围绕行程安排

站台上并排停靠的两列新干线
假设前几天只在东京市区活动,之后才开始连续的长途移动,就应把两段费用分开比较。 机场当天领到票,不意味着必须当天启用。 反过来,如果之后没有足够多的适用路段,推迟启用也不会让一张不合适的券自动变划算。
列出每天真正想走的路线,再算票价,比把东京、日光、仙台、平泉与关西硬塞进几天更有意义。 换乘、寄存行李、用餐和休息也占时间,铁路行程需要为这些留出空间。
使用期间按规定携带护照与票券,保管好不能轻易补发的原件。 退款、变更和遗失处理,各票种条件不同,购买时一并查清,旅行中就能少一点临时处理手续的负担。
### 日本自驾旅行指南
URL: https://www.sakashita.page/japan-road-trip-guide/
Last updated: 2026-10-24T15:30:00.000Z
日本自驾的准备,既包括能否合法驾驶,也包括每天怎样把车开到、停好、再顺利归还。 租到一辆车,只是行程的起点。 把文件、车辆条件、费用与道路安排分开确认,会比照抄一张旧租车价目表更实用。
## 先确认驾驶资格
持台湾签发的有效驾照驾驶时,需要驾照原件、法令认可机构制作的日文译本,并准备可确认入境日期的文件。 译本不能代替原件,可驾驶期间也受上陆日期、驾照有效期与住民登记等条件限制。 具体制度见[警视厅的外国驾照说明](https://www.keishicho.metro.tokyo.lg.jp/menkyo/menkyo/kokugai/gaikokumenkyo.html)。
其他驾照与国际驾驶许可证的适用方式不同,另见[驾驶证与所需文件](https://www.sakashita.page/driving-licenses-in-japan/)。 先确定自己的文件适用,再做不能灵活取消的租车安排。
## 订车时,看清总费用与取还车条件
车型、日期、取还车门店与附加服务都会影响价格,不存在适合全国、全年套用的一张日租金表。 比较报价时,把儿童座椅、异地还车、ETC卡、保险与补偿等项目一并算入。 座位够不够,也要与行李能不能放下分开判断。
取车所需原件、付款方式与追加驾驶者登记,按门店要求准备。 还车时间应留出加油、找入口和检查车辆的余量,不把导航显示的抵达时间当成手续全部完成的时刻。
## 取车以后,先熟悉车辆
与工作人员一起确认外观、既有损伤与随车设备。 离开之前,也问清燃料种类、紧急联络方法、事故处理流程与保障范围。 免赔额减免或营业损失补偿各有条件,不能把“买了保险”理解为任何情况都不再承担费用。
导航、雨刷、灯光与挡位的操作,先在停车状态熟悉。 要查路线或使用手机时,选择允许且安全的位置停好车,不临时停在车流与路口里处理。
## 加油:先认准油种
普通汽油、优质汽油与柴油是不同燃料。 日文“軽油”指柴油,并非“轻型汽车用油”,不能根据车身大小猜测。 [JAF的加油说明](https://jaf.or.jp/common/kuruma-qa/category-drive/subcategory-knowledge/faq258)也特别提醒了这一点。
自助与人工服务的付款步骤不同,按油站与设备说明操作。 是否满油还车、使用哪种结算方式,应遵守租赁合同,电动车则另有充电与归还条件。 不要为了找一家略便宜的油站,把返程时间压得太紧。
## ETC:设备与卡要一起确认
有ETC车载器,不代表已经有可用的ETC卡。 租车时应确认卡是否另租、怎样插入、费用怎样结算,以及计划中的收费站是否支持自己的通行方式。
部分收费站已为ETC专用。 如果误入,不能在车道上倒车,应按现场“サポート”或“ETC/サポート”标志停车求助,服从工作人员指示。 详细说明见[NEXCO东日本的ETC专用收费站页面](https://www.driveplaza.com/etc/etc%5Fguide/etc%5Fonly%5Fstation/)。
## 每天的路线,给停车与休息留时间
靠左行驶、行人优先、限速、儿童座椅与安全带等要求,都应在出发前认真了解。 [JAF的交通规则入口](https://english.jaf.or.jp/safe-driving/traffic-rules-in-japan)提供了进一步的准备资料。 遇到不熟悉的标志或道路,不用“跟着前车走”代替判断。
山区、海岸与冬季道路还受天气和通行限制影响。 先看当天路况,减少不必要的长距离折返,把吃饭、休息和停车后的步行也放进行程。 车能带我们去公共交通不易抵达的地方,但它不必成为一天塞进更多景点的理由。
### 日南海岸自驾:青岛、太阳花园与鹈户神宫
URL: https://www.sakashita.page/nichinan-coast-road-trip/
Last updated: 2026-10-24T15:19:59.000Z

日南海岸旅行中看到的彩色绘马与祈愿牌
从宫崎机场租车出发,沿国道220号向南,海面和蓝天逐渐成为车窗外的主角。 这次的顺序是青岛、日南太阳花园,再到鹈户神宫。 三处停靠点各有不同的步行路段,路上也值得留些余地。
## 青岛:先停车,再步行过桥
抵达青岛一带后,把车停在岛外,再步行经过弥生桥入岛。 小岛上有浓密的亚热带植物,海岸露出的波状岩则为风景添上了鲜明的纹理。
### 随海风展开的神话
青岛神社与山幸彦、海幸彦的故事紧密相连。 神话中,擅长打猎的山幸彦借了哥哥海幸彦的鱼钩,却不慎把它弄丢,只得前往海神的宫殿寻找。

神话人物与鲷鱼造型的展示
在那里,山幸彦与海神的女儿丰玉姬结为夫妻,并找回了鱼钩。 临别时,海神赠予能控制潮汐的宝珠。 青岛被传为山幸彦从海宫归来后生活的地方,因此海岸、树林和神社也被赋予了故事的层次。
想把沿途的传说串起来,可以先读[日向神话路线](https://www.sakashita.page/hyuga-mythology-route/)。
## “鬼之搓衣板”:海浪留下的纹理

青岛海岸层层排列的鬼之搓衣板岩层
这一带的波状岩被称为“鬼之搓衣板”。 它们来自砂岩与泥岩交替堆积的地层,地层隆起后,海浪长期侵蚀软硬不同的岩层,留下整齐起伏的形态。

沿日南海岸延伸的波状岩与海景
青岛周边的相关地质景观被指定为日本国家天然纪念物。 退潮时,露出的岩面更多,可以从岸边观察层次;湿滑处和浪打得到的位置需要格外留意。
[青岛“鬼之搓衣板”的形成与参观信息www.miyazaki-city.tourism.or.jp](https://www.miyazaki-city.tourism.or.jp/spot/10137)

青岛海水浴场公园入口与远处的小岛
海边的青岛海水浴场公园有季节性店铺和休息空间。 如果午后还要继续向南,青岛这一站可以把散步与午餐合并安排。

青岛海边幸福的黄色邮筒
桥头附近的黄色邮筒很醒目。 寄一张明信片,或只是停下来看看海,都适合作为上岛前的一小段休息。
## 青岛神社与植物园
青岛神社供奉山幸彦、丰玉姬和盐筒大神,与结缘、航海等信仰相连。 参道通向岛内的树林,绘马和祈愿小物也有自己的趣味。

青岛神社描绘一对人偶的结缘绘马

青岛神社鲷鱼造型的祈愿小物
人们把愿望写在绘马上,或带走一只鲷鱼造型的小物,留下这次参拜的记忆。 这些祈愿习俗也让神话与今天的旅行接在了一起。
[青岛神社官方网站aoshima-jinja.jp](https://aoshima-jinja.jp/)

青岛亚热带植物园的花坛与树木
岛外的宫交Botanic青岛植物园适合接着散步。 花坛、草坪与热带植物,让这一带的南国气息更鲜明。 温室和园地的开放安排可查[植物园官方网站](https://mppf.or.jp/aoshima/)。
## 日南太阳花园:七尊摩艾像与海景
接着沿国道220号向南,到日南太阳花园。 这里面向大海排列着七尊复刻的摩艾像,斜坡、草地和开阔海面构成了很有辨识度的风景。

日南太阳花园面向大海的七尊摩艾像
园方为不同的摩艾像赋予事业、恋爱等祈愿主题,可以把它们当作游园时的小趣味。 园内还要步行上下坡,安排时间时请把拍照和休息都算进去。
[日南太阳花园:摩艾像与海景散步](https://www.sakashita.page/sun-messe-nichinan/)
## 鹈户神宫:沿海崖走向岩窟
从太阳花园继续南下,下一站是鹈户神宫。 前往本殿的路上有海崖、台阶和朱红色栏杆,风景从开阔的海面渐渐转入岩窟。

鹈户崎灯台

鹈户神宫沿海崖延伸的朱红色栏杆
本殿建在岩窟中,神社将这里视为丰玉姬诞下鸬鹚草葺不合尊的传说之地。 神话里,产房的屋顶尚未用鸬鹚羽毛葺完,孩子便已出生,这也是神名故事的一部分。

鹈户神宫岩窟内的参拜空间
岩窟中的乳岩、临海的龟石等,各有与育儿和祈愿相关的传说。 听完故事再看地形,更能体会这处神社与海岸之间的关系。
[鹈户神宫境内与参观信息www.udojingu.or.jp](https://www.udojingu.or.jp/keidai/)
### 旅途中带回的小物

相关配图:从青岛神社带回的红色鲷鱼造型小物

相关配图:青岛神社鲷鱼造型小物的另一种配色
这些是在青岛神社看到或带回的小物。 同样是海边的神社,青岛与鹈户的气氛却很不同,放在同一天走,沿途的变化很有意思。
## 自驾怎样安排
- 🚗取车后:从宫崎机场前往青岛,在岛外停车,步行参拜和看海。
- 中段:沿国道220号南下,在太阳花园安排游园与休息。
- 下午:参拜鹈户神宫,再按还车时间返程。
取车、停车和往返本殿都需要时间,不适合只把导航显示的车程相加。 遇到强风、大雨或道路管制时,请调整海岸行程。
租车前先确认自己持有在日本有效的驾驶文件,以及租车公司的核验要求。 可参考[在日本驾驶所需的证件](https://www.sakashita.page/driving-licenses-in-japan/),并提前约好取还车地点。
### 日南太阳花公园与摩艾石像
URL: https://www.sakashita.page/sun-messe-nichinan/
Last updated: 2026-10-24T15:19:59.000Z
日南太阳花公园位于宫崎县的海岸丘陵。 七尊摩艾石像、蓝色海面与坡上的装置,是这次照片里反复出现的景象。 它可以接进[日南海岸的自驾路线](https://www.sakashita.page/nichinan-coast-road-trip/),也适合单独留一段时间慢慢走。
## 摩艾石像为什么来到日南
园方介绍,日本团队参与复活节岛石像修复后,获得当地长老会许可,在日本制作了七尊石像的复刻。 这个过程可以在[公园的历史页面](https://sun-messe.co.jp/about/)里看到。 日南的石像是后来制作的复刻,并非从复活节岛搬来的古代原物。
园内也把不同愿望与七尊石像联系起来。 这些是观光与祈愿文化中的说法,不是石像能够保证事业、健康或爱情结果的证据。 站到稍远的位置看看石像与海岸的关系,景色本身就很有力量。

公园里的红色爱心拍照装置
## 从石像广场走上丘陵

面向海景设置的彩色人物雕塑
坡上的彩色人物雕塑让人忍不住换几个角度观察。 人物、长椅、天空与海面之间的位置不同,照片里呈现的关系也会改变。 把装置看成空间的一部分,比只想着站到某个固定位置摆姿势更有趣。

太阳之丘地面上的巨大蝴蝶图案
太阳之丘上的蝴蝶图案,可以沿路径从不同高度看。 这里有坡道与台阶,拍照停留时也给通行的人留出位置。

公园中心区域的天空之塔
中心区域还有展览与室内空间,具体展出内容应看当日安排。 遇到阳光太强或天气变化,也可以把室内参观与户外散步交替起来。

沿日南太阳花园阶梯望向太平洋
## 地球感谢之钟一带

21世纪出发之灯
公园通过钟、纪念装置与展示表达对地球和和平的愿望。 这些设施有各自的设置背景,参观时读一读现场说明,会比把所有装置都当成同一组拍照道具更具体。
这里沿丘陵展开,走到高处以后还要考虑下坡。 如果打算使用园内游览车,先向工作人员确认当天的租用、驾驶资格与乘坐条件。

游园车出租处
## 把到园与离园一起安排
乘巴士时,先看日南方向的实际班次与站点,再留出从巴士站进入公园的时间。 自驾则把停车、步行与返程也算进去,不只估算景点之间的开车分钟数。

旅行时留下的导览册与门票
旧门票与导览册保留的是当时的信息。 现在再去,应看[公园的使用说明](https://sun-messe.co.jp/information/)与营业日历。 园方在2026年增加了部分星期三以外的休园日,出发前仍需确认具体日期。
沿着坡路看海、在喜欢的装置前停一停,再回到石像广场回望,这段游园就有了起伏。 天气与体力合适时慢慢走,比为了拍齐所有位置匆匆赶路更舒服。
### 日本旅行小常识
URL: https://www.sakashita.page/japan-travel-basics/
Last updated: 2026-10-24T15:09:59.000Z
日本旅行的准备,可以从几件具体的事开始:行李能不能带、到达后怎样付款、手机怎样联网,以及每天怎样移动。 这些问题有不同的规则与服务条件,不适合只凭一张旧价格表解决。 先把自己会用到的部分确认好,抵达以后就能把精力留给旅行本身。
## 出入境:按物品查规定
药品、食品、动植物及其制品,可能涉及不同主管部门的许可与检疫。 个人自用、在机场买到或数量很少,都不自动代表可以携带入境。 尤其是处方药与受管制成分,不能只看商品名称判断。
出发前可从[日本海关的旅客通关说明](https://www.customs.go.jp/english/summary/passenger.htm)查起,再沿其中链接确认药品、动物检疫与植物检疫的具体要求。 不要把“禁止”“需要证明”与“超过限量要办理手续”混成同一种情况。 不确定的物品,应在打包之前向相应部门确认。
## 现金与支付
日元常用纸币与硬币并行,部分地方仍需要现金。 交通IC卡、信用卡、二维码支付与现金的适用范围不同,不能假设手机里有一种支付方式就能覆盖全程。
准备适量现金与备用付款方式,通常比临时寻找兑换窗口更省事。 兑换外币或使用ATM之前,确认营业时间、可用卡种与费用,不要把所有银行、邮局都当成随时能兑换的地点。
在一般餐厅与住宿设施,通常不需要另外给小费。 账单上若有服务费,则是另一项计价内容;按店方说明结算即可。
## 上网:先看手机与方案是否相容
可以比较国际漫游、旅游SIM、eSIM与租用移动路由器。 选择时先看手机是否支持、是否需要解锁、流量与有效天数,以及能否接收自己需要的短信。 只看“无限流量”几个字,可能漏掉速度限制或使用条件。
公共Wi-Fi可以作为补充,但覆盖、登录方式和稳定性不同。 出门前把住宿地址、关键车票与地图留一份离线副本,也能减少网络暂时不可用时的困扰。
## 自驾与公共交通
日本车辆靠左行驶,自驾还涉及驾驶资格、车辆类别、保险与租车门店要求。 具体文件另见[来日本自驾:驾驶证与所需文件](https://www.sakashita.page/driving-licenses-in-japan/)。 高速公路有ETC等不同收费方式,不能把“入口取票、出口交钱”当成所有车道都相同的流程。
如果没有把握完成驾驶与取车准备,铁路、巴士和出租车也可以组成旅行。 先看最后一段交通与返程时间,再选住宿和景点,比到站以后才发现没有合适接驳更从容。
## 租自行车:查当地方案
各地租车服务的价格、年龄限制、付款方式与还车地点都不一样。 原来某个城市的一次租车方案,不能推广成日本全国统一的收费规则。
租以前看清可骑范围、停车方式、保险和最晚还车时间。 电动辅助车也不是没有交通规则的代步玩具,遇到坡路、雨雪或不熟悉的车流时,可以选择推行或换一种交通方式。
## 出发前留下几项可用的信息
把住宿的准确地址、紧急联络方式、重要预约与返程路线放在容易找到的位置。 有需要时,也可以从[日本国家旅游局的旅行准备页面](https://www.japan.travel/en/plan/)继续查官方入口。 准备不必包揽所有可能,能支持自己每天真正要做的事,就已经很有用。
### 来日本自驾:驾驶证与所需文件
URL: https://www.sakashita.page/driving-licenses-in-japan/
Last updated: 2026-10-24T14:59:59.000Z
来日本自驾,第一步是确认自己持有的文件是否允许在日本驾驶。 驾驶证、国际驾驶许可证、日文译本与租车公司的取车要求,分别解决不同问题。 不要只凭“国际驾照”这个商品名称,或他人的一次租车经历,就判断自己也能使用。
## 先分清三种制度
### 日本驾驶执照
持有日本签发、适用于所驾车辆且在有效期内的驾驶执照,可以按其准驾条件驾驶。 外国驾照换领日本驾照则是另一项申请手续,不是把外国驾照直接交给租车公司就能完成的转换。
换领的条件包括取得外国驾照后,在发照国家或地区累计居留至少三个月。 申请还涉及住所证明、材料审核,以及按规定进行的适性、知识和技能确认。 短期游客不能把这条途径当成落地后立即租车的准备。 最新资格与材料要求,请查[警视厅的外国驾照换领说明](https://www.keishicho.metro.tokyo.lg.jp/menkyo/menkyo/kokugai/kokugai05.html),并向办理地的警方确认。
### 1949年《日内瓦公约》格式的国际驾驶许可证
日本认可的国际驾驶许可证必须符合1949年《日内瓦公约》的制度与格式要求。 一些国家虽然是公约缔约方,却不签发相应格式的许可证,因此不能只查国家是否加入公约。
许可证的签发资格、车辆类别、有效期与入境后的可驾驶期间,都需要核对。 中国大陆驾照本身或一般翻译件,不会因此变成可在日本使用的国际驾驶许可证。 可先查[警视厅的公约缔约方与签发情况](https://www.keishicho.metro.tokyo.lg.jp/menkyo/menkyo/kokugai/kokugai04.html),再查[日本警察厅的驾驶执照问答](https://www.npa.go.jp/policies/application/license%5Frenewal/QA.html)。
### 指定国家或地区的驾照与日文译本
瑞士、德国、法国、比利时、摩纳哥及台湾签发的驾照,适用另一项制度。 驾驶者须携带有效驾照原件,以及法令认可机构制作的日文译本。 台湾驾照属于这一项,不应与国际驾驶许可证混为一谈。
可驾驶期间原则上不得超过上陆日本后一年,也不能超过驾照本身的有效期。 在日本登记住民的人员,短期出境后再次入境还涉及“三个月规则”,不是每次入境都会重新起算一年。 认可的翻译机构和具体条件,见[警视厅的外国驾照使用说明](https://www.keishicho.metro.tokyo.lg.jp/menkyo/menkyo/kokugai/gaikokumenkyo.html)。
## 在日本生活,准备考取驾照
如果不适用换领制度,也可以了解日本驾驶学校与考试的安排。 报名条件、教学语言、练习与考试方式,应向学校和考试场分别确认。 “用中文授课”与“所有手续、考试都能用中文完成”并不是同一回事。
选学校时,比起只看最快几天能拿证,更应确认自己的在留与居住情况是否符合要求,以及能否安排持续的练习和考试时间。
## 订车以前,向门店核对什么
确认驾驶资格以后,再把取车所需文件、付款方式、驾驶者人数与车辆类别逐项核对。 不同驾照制度下,门店要求出示的原件可能不同,不要只带手机照片或自行打印的译文。
保险与补偿也应看清承保范围、免赔额、营业损失补偿,以及哪些使用方式不在保障内。 购买附加保障不等于所有损失都有人承担,遇到事故仍须按警方和租车公司的要求处理。
如果目前的文件不适用,就先用铁路、巴士或出租车安排旅行。 把驾驶资格确认好以后再订车,才不会到了柜台才发现行程无法开始。
### 宇都宫近郊:大谷资料馆、多气山与若竹之杜
URL: https://www.sakashita.page/utsunomiya-caves-and-bamboo/
Last updated: 2026-10-24T14:49:59.000Z
宇都宫近郊的这条路线,把地下采石场、山寺参道与竹林连在一起。 三处景色各有温度与光线,也不在同一段可以轻松步行到达的街道上。 出发前先决定自驾、巴士或出租车怎样衔接,再按体力减少停留点,会更容易走得从容。
## 大谷资料馆:先认识石,再走进地下

大谷资料馆爱之泉
0:00
/0:10
1×
大谷资料馆入口附近泉水景观的视频
大谷资料馆介绍大谷石的开采与利用,地下采石场则把开采留下的空间直接展开在眼前。 这不是天然洞窟,而是人们取石以后形成的人工地下空间。 运营方的[采掘场介绍](https://www.oya909.co.jp/contents/%E5%A4%A7%E8%B0%B7%E7%9F%B3%E5%9C%B0%E4%B8%8B%E6%8E%A1%E6%8E%98%E5%A0%B4%E8%B7%A1/)记载了1919年至1986年间的开采历史。

大谷资料馆入口与参观设施
地面与地下的温差明显,按当天馆方显示的温度准备衣物,比用一个固定的“夏天11度”概括全年更可靠。 地下有台阶与潮湿路面,也要给慢慢行走和适应光线留出时间。

大谷地下采石场的石壁与空间
0:00
/0:14
1×
大谷地下采石场参观视频
## 石壁上的开采痕迹
大谷石是一种凝灰岩,在宇都宫的建筑、围墙与装饰中都能见到。 材料有自己的加工与使用特点,但一栋采用大谷石的建筑经历地震,并不能证明这种石材本身就有普遍的抗震或防火保证。

大谷地下采石场入口附近的巨大石柱

大谷资料馆地下空间的切石纹理与灯光
在地下看石柱、切痕与灯光,可以留意人怎样一层层把石材取走,又为支撑空间留下必要部分。 现在这里也被用于演出、拍摄与其他活动,具体开放区域随安排变化,参观按现场路线进行。
## 多气山:换到参道与绣球花
多气山持宝院,也常以多气山不动尊的名称受到参拜。 它位于山腰,参道与登山路线不是同一个强度的活动。 [宇都宫官方景点介绍](https://www.utsunomiya-cvb.org/spot/detail%5F10041.html)说明了寺院与季节花景,梅雨季的绣球花可以成为安排停留的理由。

多气山参道旁盛开的绣球花
0:00
/0:14
1×
多气山参道绣球花的视频
这次天气晴好,我们也沿参道看了花。 如果从大谷资料馆继续过来,应按实际道路与交通方式估时,不把两点在地图上相近理解成走起来没有坡路。
## 若竹之杜:这里的“杜”是树林
若山农场的设施名称是“若竹之杜”,不是“若竹之社”。 这里由持续经营的农场照料竹林,并开放部分空间供人参观,不能仅因名字就把它当成一座神社。

若山农场高耸而浓密的竹林
竹林里的光线与刚才的地下空间很不同。 向上看竹竿之间的距离,再停下来听叶片与风,散步不必围绕拍摄同一个机位展开。
竹器饮茶、餐饮、工作坊与点灯各有安排。 天然材质也不自动等于所有用途都绝对安全,购买与使用仍应看具体产品说明。

若山农场商店的白墙入口
最新营业、点灯日期与体验内容,可查[若山农场官网](https://www.wakayamafarm.com/)。 如果想留到夜里,先把返程交通安排好;没有夜景计划时,白天认真走一段竹林,也已经能形成完整的停留。
## 这一天怎样取舍
自驾比较容易串联三处,使用公共交通则需分别核对接驳,不能假设有一张票覆盖所有路线。 只有半天时,可以选大谷资料馆,再加一处最感兴趣的地点。 从石壁走到竹林,景观的变化已经足够鲜明,不需要继续增加更多停留点。
### 日本的祈福文化
URL: https://www.sakashita.page/prayer-and-good-luck-in-japan/
Last updated: 2026-10-24T14:40:00.000Z
旅行时,常会看见人们在神社或寺院合掌、鞠躬,在绘马上写下愿望,或为家人挑选御守。 这些行为既可能出于个人信仰,也可能与节庆、家庭习惯和人生节点有关。 不能因为参拜者很多,就推断所有日本人都抱有相同的宗教观念。
## 先分清神社与寺院
神社与寺院有不同的信仰背景,参拜礼仪也不完全相同。 神社常见鞠躬与拍手的方式;寺院一般合掌祈愿,不照搬神社的拍手仪式。 即使同属神社,个别地方也有自己的礼法,现场说明应放在通用介绍之前。
不熟悉仪式时,可以先安静观察,不必因为担心动作不标准而匆忙模仿。 参观、拍照与参加宗教仪式也不是一回事,可以按自己的意愿选择。
## 绘马、御守与御神签
- **绘马**:写下祈愿后,通常悬挂在指定位置。
- **御守**:人们依照学业、交通、健康等愿望选择携带的护符。
- **御神签**:阅读签文中的提醒;是否系在境内、系在哪里,应看当地说明。
这些物件寄托了人们的愿望,不是效果可以保证的商品。 挑选时,读懂文字和用途,比只看颜色或造型更有意思。
## 在日常与节庆之间
新年初次参拜称为“初诣”,但人们也会在考试、旅行或其他重要时刻前往参拜。 对旅行者来说,读一块介绍祭神或寺院由来的说明牌,也能为眼前的仪式补上背景。
境内仍是他人祈祷与生活的地方。 不要拍摄绘马上的私人信息,遇到法事或祈祷活动时留出空间;看到禁拍标识,就把相机收起来。
### 神武天皇与日本的建国传说
URL: https://www.sakashita.page/emperor-jimmu/
Last updated: 2026-10-24T14:29:59.000Z
在宫崎、熊野或奈良旅行时,常会遇到与神武天皇有关的神社和传说。 理解这些地方,首先要分清两个层面:神话如何讲述国家的起源,以及史料与考古能够确认什么。
## 传说中的第一代天皇
《古事记》《日本书纪》把神武天皇记作日本的第一代天皇,并讲述他从日向出发、前往大和的故事。 这些记述构成了日本建国神话的重要部分。 阅读时应把它们视为古代文献中的叙事,不能直接当作可以逐项确认的行军记录或现代意义上的人物传记。
## 熊野与八咫乌
神武东征的故事里,熊野是重要的一站。 八咫乌作为引路的神鸟,帮助队伍前往大和,也因此与熊野信仰紧密相连。 不同古典记述的细节有所差异。 例如[国学院大学对高御产巢日神的解说](https://kojiki.kokugakuin.ac.jp/shinmei/takamimusuhinokami/),便介绍了神明遣八咫乌引导神武的情节。
## 旅行中怎样读神话?
神社的缘起、地名和祭典,往往保存了当地讲述这些故事的方式。 把它们放在景观与信仰的背景中阅读,比急着判断每一处是否“确有其事”,更容易看见故事怎样与地方相连。
如果沿着宫崎的海岸旅行,也可以结合[日向神话相关的旅行路线](https://www.sakashita.page/hyuga-mythology-route/)阅读。 把地名、景观与故事放在一起阅读,会为海边的神社与山间的道路添上一层文化背景。
### 日向神话街道之旅
URL: https://www.sakashita.page/hyuga-mythology-route/
Last updated: 2026-10-24T14:19:59.000Z
宫崎的神社、海岸与山地,经常能遇到同一组神话人物。 天孙降临、海幸彦与山幸彦、丰玉姬的产屋,以及神武东征,构成了认识这一带传说的线索。
“日向神话街道”把县内国道、县道与林道等不同道路串联起来,从北部的高千穗延伸至高原町方向。 它适合分区域慢慢旅行。 青岛和日南海岸又提供了理解海幸彦、山幸彦故事的另一条线,可以另作一段沿海行程。
## 先在青岛认识故事中的人物
青岛神社境内的日向神话馆,以30尊蜡像与12个场景,表现从天孙降临到神武天皇故事的一系列传说。 如果对人物关系还不熟悉,这里可以作为入门。
这些故事见于《古事记》《日本书纪》等古代文献,但不同文本与地方传承之间也有差异。 旅行中介绍的,是神话与信仰如何解释世界、家族和王权的来历。 它们不能直接代替考古与历史研究。
[青岛神社:日向神话馆aoshima-jinja.jp](https://aoshima-jinja.jp/mythology%5Fbuilding/)
## 天孙降临与木花咲耶姬
故事从天照大神命孙子琼琼杵尊降临人间开始。 镜、剑与勾玉等宝物,成为后来有关王权传承叙事的重要象征。
来到地上后,琼琼杵尊遇到山神的女儿木花咲耶姬。 山神原本希望他同时迎娶姐姐磐长姬,以岩石与花朵分别寄托长久与繁荣。 琼琼杵尊只接受木花咲耶姬,于是人的寿命如花般有限,也成为这个选择在神话里的结果。
后来,木花咲耶姬为证明孩子的出身,在燃烧的产屋中分娩。 这是神话中的情节,相关场景应放在故事与象征的层面理解。
## 丢失的钓钩,把山幸彦带向海中
木花咲耶姬所生的孩子中,海幸彦擅长捕鱼,山幸彦擅长狩猎。 两人交换工具后,山幸彦弄丢了哥哥的钓钩。 即使打造许多新钩,也无法让哥哥接受,他只好设法寻找原来的那一枚。
在老翁的指引下,山幸彦来到海神的宫殿,与丰玉姬相遇并成婚。 几年之后,他想起钓钩的事情,海神便召集鱼类,最后从鲷鱼口中找到了钩子。
青岛神话馆的多个场景,都围绕这趟海中之旅展开。 先记住“借工具、丢钓钩、到海神宫、找回钩子”这条主线,人物多时也比较容易跟上。
## 潮汐宝珠与丰玉姬的产屋
山幸彦回到陆地时,海神赠给他能操纵潮汐的宝珠。 后来兄弟发生冲突,山幸彦用宝珠使海幸彦屈服。
丰玉姬将要生孩子,来到陆地上的产屋,并要求丈夫不要观看分娩。 山幸彦违背约定,看到了她作为海神之女的另一种形态。 丰玉姬因此回到海中,把孩子留在陆地,由妹妹玉依姬照顾。
这个孩子就是鸬鹚草葺不合尊。 日南海岸的鹈户神宫,便与这一段传说密切相关。 走近海边洞窟时,知道产屋与屋顶未完成的故事,会更容易理解神社介绍中的名字和图像。
## 从神话家族到神武东征
鸬鹚草葺不合尊与玉依姬所生的孩子中,后来被称作神武天皇的人物,从日向向东前往大和。 有关出发地、途中停留和各地遗迹,沿途也留下了许多不同的地方传说。
这些叙事把宫崎与日本皇室的起源联系起来。 理解它们的意义,可以同时观察古代文献、地方信仰与今天的祭祀文化,并保留神话年代与历史证据之间的距离。
## 怎样把故事放进旅行
- 🚗 山地路线:以高千穗等地区为一段,先查山路状况、季节与住宿,再决定一天走多远。
- 🚃 沿海路线:青岛与日南方向可另查铁路和公交,按班次选择参观地点。
- 🚶 到神社后:先看祭神与由绪介绍,再把当地说法接回故事的主线。
同一则神话可能对应多个地点,旅行中也会遇到不同版本。 在海边、山间和展馆里分别认识它们,比急着寻找一个唯一的“故事现场”更有意思。
[宫崎县官方观光信息www.kanko-miyazaki.jp](https://www.kanko-miyazaki.jp/)
### 日本街头的地藏像
URL: https://www.sakashita.page/jizo-statues/
Last updated: 2026-10-24T14:09:59.000Z
在日本旅行,寺院、街角或乡间路旁常能见到小小的地藏像。 有的披着红色围兜、戴着帽子,有的面前摆着鲜花。 这些细节使石像带着一种接近日常生活的亲切感。
## 地藏菩萨与人们的祈愿
在佛教信仰中,地藏菩萨被视为救助苦难众生的菩萨。 在日本的民间信仰里,地藏也常与儿童的平安、逝者的追思等祈愿相连。 不过,每一尊像的来历并不相同,不能只凭红色衣帽,就断定它一定与某一种供养仪式有关。
部分寺院举行“水子供养”,为未能平安出生或早逝的孩子祈祷、寄托追思。 例如[京都化野念佛寺对冬日供养的介绍](https://nenbutsuji.jp/shiki/winter/),就记录了人们向地藏合掌、祈愿孩子得到安宁的场景。 这是信仰与悼念的表达,具体方式因家庭与地方而异。
## 路过时,留意身边的照料
衣帽、鲜花和清扫过的石台,也让人看见一尊地藏像与周围社区的联系。 如果现场有说明牌,可以先读读它的名称、由来和供奉者的故事。 拍照时不移动供品,也不要打扰正在参拜的人。
地藏像不只是旅行照片里的小装饰。 在热闹的景点之外,它们也提醒我们:一条普通的街道,同样承载着人们长期的祈愿与记忆。
### 秩父街头漫步
URL: https://www.sakashita.page/chichibu-town-walk/
Last updated: 2026-10-24T13:59:59.000Z
秩父位于埼玉县西部。 市区的神社、街道与山间的季节景观,适合用不同节奏来认识:半天在街头散步,另留时间前往外围景点。 同属秩父的景点,实际相距也可能很远,需要分别安排交通。
## 从市区与秩父神社开始
第一次来,可以以车站周边和秩父神社为散步中心。 留意神社社殿上的雕刻,再走回街区找地方吃饭,比一到站就赶往多个远郊景点更从容。 店铺的休息日不尽相同,午餐最好不要拖得太晚。
## 秩父夜祭的热闹
秩父夜祭是当地重要的祭礼,通常在12月2日至3日举行。 山车与烟火使冬夜格外热闹,平日安静的街道也会迎来大量人潮。 若专程为祭典而来,交通管制、观览区域和返程安排,应以当年的主办方信息为准。
## 冬天的“三大冰柱”
冬季,秩父山间的寒冷造就了壮观的冰柱景观。 常说的“三大冰柱”包括三十槌、尾之内和芦久保三处;它们分布在不同地点,不能当作市区里相邻的三个景点。 [秩父观光协会的冰柱介绍](https://www.chichibuji.gr.jp/event/turara/)可用来核对当季开放与点灯安排。
冰柱的状态受气温影响,开放日期不宜从旧游记照抄。 计划傍晚看点灯时,也要预留返回车站的交通时间。
## 给一日游留一点余地
市区散步、赏花、看红叶和冬季冰柱,各有适合的时间与路线。 先选一个主要目的,再补上附近的一两处停留点,会比把所有季节的看点塞进同一张行程表更实用。
### 认识日本温泉:泉质、浴场与旅馆怎样选
URL: https://www.sakashita.page/japanese-hot-springs/
Last updated: 2026-10-24T13:49:59.000Z
选择温泉旅行时,泉质、浴场、住宿与周边散步,往往会同时影响体验。 先读懂设施提供的信息,再按交通和自己的习惯选择,比只追着一个“名汤”称号更容易安排。
这篇从温泉的认定与现场说明谈起,再看看温泉街和旅馆可以怎样放进行程。
## 什么样的水叫温泉
日本《温泉法》的认定,主要看从地下涌出的温度与规定的成分标准。 就温泉水而言,源泉采集时达到25℃,或未达到这个温度但符合规定的成分条件,都可能属于法律上的温泉。 法律还涉及规定范围内的蒸气与气体,实际定义应以正式条文和环境省说明为准。
这里的温度是源泉采集时的数值,与浴池中适合入浴的水温不同。 因此,温泉水可能经过降温,也可能需要加热后才用于浴场。
[日本环境省:温泉的定义www.env.go.jp](https://www.env.go.jp/nature/onsen/point/index.html)
## 怎样读泉质名称
浴场入口常能看到温泉分析书、泉质名称与使用说明。 氯化物泉、碳酸氢盐泉、硫酸盐泉、含硫泉等名称,反映了特定的成分与分类条件。 “单纯温泉”也有分析标准,并不表示没有任何成分。
泉质之外,泉温、酸碱度与实际利用方式,也会改变入浴时的感受。 同一温泉地的不同源泉,甚至同一旅馆的不同浴池,都可能采用不同的水。
阅读分析书时,可以先找这几项:
- 源泉名称与分析日期:确认说明对应哪一股水。
- 泉温与酸碱度:了解分析时的基本状态。
- 浴场利用方式:看是否加水、加温、循环过滤或消毒。
- 入浴说明:确认现场提供的限制与注意事项。
分析值的单位与测定条件都很重要。 不能把其他地区的分类标准、毫克数或宣传用语,直接套进日本的温泉介绍。
## “源泉”与浴池之间,还有管理过程
源泉水温很高时,设施需要让它降到适合利用的温度;水温较低时,也可能进行加温。 循环过滤与消毒,则与浴场的管理方式有关。
日本对这些利用方式有相应的表示要求,浴场会说明做了什么处理及其理由。 选择时可以把表示与浴池环境一起看,不必只凭“天然”两个字推定所有条件。
[日本环境省:温泉利用表示www.env.go.jp](https://www.env.go.jp/nature/onsen/pdf/2-5%5Fp%5F43.pdf)
## 从泡汤到温泉街
日本的温泉地,有适合停留几天的山间旅馆,也有车站附近的公共浴场。 有人把温泉放在登山或城市旅行之后,有人则围绕住宿、晚餐与散步专门安排一晚。
历史上的汤治,是理解温泉文化的一条线索。 今天的观光住宿与当年的疗养生活各有背景,介绍它们时,也应保留历史记载、民间说法与医学判断之间的区别。
旅途中,可以观察浴场之外的部分:水源怎样引入街区,公共浴场与旅馆怎样分布,商店和居民生活又怎样围绕温泉发展。 这些细节能让一次泡汤变成认识地方的入口。
## 以伊香保为例
伊香保的365级石阶,把旅馆、店铺与神社串在一条有高差的街道上。 如果只安排一次入浴,很容易匆匆经过;住一晚或留出半天,则能看见石阶街在不同时段的样子。
德富芦花的小说与晚年经历,又为这里加上文学的一层。 这部分可以另读[伊香保与德富芦花](https://www.sakashita.page/ikaho-and-tokutomi-roka/),再按兴趣选择旅馆、文学馆与散步路线。
石阶街、榛名湖与水泽寺之间需要分别安排交通。 想走得轻松,可以先选温泉街作为一个重点,再决定是否增加郊外景点。
## 怎样选择一晚住宿
预订温泉旅馆时,先把最影响行程的条件看清楚:
- 交通:车站、公交与接驳是否衔接,末班何时离开。
- 浴场:大浴场、露天浴场、包租浴池与客房浴池分别怎样使用。
- 房型:和式、洋式、寝具与无障碍条件是否适合同行者。
- 餐食:住宿方案是否包含晚餐和早餐,是否有抵达时间限制。
客房里有浴缸,不一定使用温泉水;露天浴场也可能有独立的时间或预约安排。 想要哪一种体验,就在预订时直接确认对应设施。
## 在现场按自己的节奏利用
温泉说明里的适应症,不能当作对个人疗效的保证。 需要治疗、想饮用泉水或对身体状况有疑问时,应先取得相应的专业建议,并遵守设施提供的利用条件。
一般的观光入浴,也可以很简单:先清洗身体,按舒适程度短时间利用,留出休息和补水的时间。 浴场内的摄影、毛巾、衣着与随身物品规则,以现场说明为准。
更具体的流程可以看[日本温泉入浴礼仪](https://www.sakashita.page/onsen-bathing-etiquette/)。 选择适合自己的浴场,再给街道、晚餐与休息留一点时间,温泉旅行便有了完整的节奏。
### 伊香保的千明仁泉亭与德富芦花
URL: https://www.sakashita.page/ikaho-and-tokutomi-roka/
Last updated: 2026-10-24T13:39:59.000Z
伊香保的石阶与温泉之外,还有一条可以慢慢阅读的文学线索:作家德富芦花与千明仁泉亭。 认识这段关系,再走进温泉街,旅馆的名字、窗外的山景和一部小说的开头,就有了可以相互对照的位置。
## 从《不如归》的开头走进伊香保
德富芦花是活跃于明治至大正时期的作家,日文署名为“徳冨蘆花”。 他的小说《不如归》开篇,便把场景放在伊香保千明旅馆三楼:一位年轻女子推开障子,望着傍晚的景色。 这不是对整座温泉街的旅游介绍,而是故事进入人物心境的起点。
想对照文字,可以阅读[青空文库收录的原作](https://www.aozora.gr.jp/cards/000280/files/1706%5F44742.html)。 先看开篇怎样写山影、夕阳与窗前的人,再去想象高处房间和周围群山的关系,是一种不必赶行程的读法。 小说中的人物与现实里的作家经历,仍要分开理解,不能把故事情节直接当成旅馆史。
## 作家留下的房间
芦花曾多次到访伊香保,1927年9月18日在这里去世。 渋川市的[德富芦花纪念文学馆](https://www.city.shibukawa.lg.jp/viewer/info.html?g=10365&id=148)介绍其生平、作品与伊香保的关系,并保存、公开从原旅馆移筑复原的别馆空间。 因此,在文学馆看的不只是展板,也包括与他生命最后阶段有关的房间。
如果对作家已有兴趣,可以把文学馆作为行程的主要停留点。 还没读过作品,也可以先了解他的生涯和温泉街的联系,回来以后再翻开小说。 企划展、开放时间与休馆日会调整,出发前查看馆方公告,避免按旧版旅行记录安排。
## 千明仁泉亭:仍在接待旅人的旅馆
[千明仁泉亭的官网](https://jinsentei.com/)说明,它曾是芦花偏爱的常宿,也是《不如归》开头的舞台。 旅馆介绍的温泉为伊香保的“黄金之汤”,设有公共浴场、露天浴场和不同类型的客房。 选择住宿时,应按实际房型确认浴池、餐食与使用条件,不能因为看见温泉旅馆的名称,就认为每个房间都配有私人浴池。
历史旅馆的乐趣,可以在房间、公共空间和留下的文字之间慢慢寻找。 馆方介绍前台附近展示有芦花写给旧时当家的信件,谈到小说《富士》完成的喜悦,细节可见[设施介绍](https://www.jinsentei.com/facility.html)。 这些有明确来历的物件,比笼统说所有旅馆都古朴、都浪漫,更能帮助人认识这一家旅馆。
官网目前注明不提供日归入浴。 只安排温泉街一日游时,应另外选择对外开放的浴场,不要把住宿设施的浴池直接排进散步路线。
## 把阅读接到石阶散步上
伊香保石阶街现在共有365级。 [渋川市的介绍](https://www.city.shibukawa.lg.jp/viewer/info.html?g=203&id=206&idSubTop=3)说明,2010年延伸石阶时,寄托了一年365天都热闹的愿望。 两侧的旅馆、商店和餐饮空间,让这条路既是观光路线,也是仍在使用的街道。
可以先在石阶街散步,再按开放时间前往文学馆;若准备住宿,则把入住和用餐时间留出来。 坡道与台阶会影响步行节奏,带着行李时尤其不必追求从头到尾一次走完。 想继续了解其他景点,可以另读[伊香保石阶街的旅行记录](https://www.sakashita.page/ikaho-onsen-stone-steps/),再按兴趣选择。
从一段小说、一封信和一间保存下来的房间出发,伊香保的文学历史就有了具体的入口。
### 伊香保温泉与石阶街
URL: https://www.sakashita.page/ikaho-onsen-stone-steps/
Last updated: 2026-10-24T13:29:59.000Z
伊香保温泉的散步,从一段接一段的石阶开始。 店铺、旅馆、足汤和神社沿坡路展开,走到高处再回望,温泉街与远山便连成了一幅景色。 这次记录留下了雨中的石阶、庭院和绣球花,也包括几段沿途拍下的视频。
## 先把交通与散步路线分开

巴士站牌
从东京方向前往伊香保,可以比较高速巴士,以及乘铁路到涩川站后换巴士的方案。 铁路经高崎换乘前往涩川,并不意味着全程都坐新干线。 巴士终点、停靠站和所需时间也随班次而异,应按旅馆或石阶街的位置确认下车地点。
当天的步行顺序是:
1. 🚶 从石阶街入口走向关所遗迹与夏威夷王国公使别邸。
2. 沿石阶上行,经过岸权旅馆的足汤与福一,再到伊香保神社。
3. 🚡 之后前往不如归站,按天气与运行情况决定是否搭缆车。

伊香保温泉导览图
这是一条带有停留的散步路线,神社与缆车站之间仍需另走一段路。 不必把365级台阶一次走完,坐下来休息、绕进小店,再继续上行也很自然。
## 温泉街的来历与两种泉源

雨天伊香保温泉街的石阶与行人
石阶街的整备可以追溯到16世纪后半。 今天的365级台阶,则是在2010年向下延伸后形成的规模,不能把现在的长度原样套回战国时代。 街道中央引水、两侧分配温泉的格局,也是理解这里如何发展成温泉街的一条线索。

御用府邸遗址
伊香保也留下了皇室、文人与外国来访者的足迹。 德富芦花的小说《不如归》以这里为故事开端,温泉街也留下了与他相关的纪念空间。 小说中的温泉街与眼前的街景,可以互相参照,却不需要为每一处建筑附会上同一个故事。

伊香保温泉街的引汤设施
当地常说的“黄金汤”与“白银汤”,来自不同泉源。 黄金汤含铁,接触空气后呈现茶褐色;白银汤则以无色透明的外观为人熟悉。 想比较两者,先看旅馆实际使用的泉源与浴池说明,不要把颜色直接等同于医疗或美容效果。
## 石阶街:把注意力放回脚边
0:00
/0:07
1×
伊香保石阶街入口
沿街可以看见旅馆入口、土产店与射击游戏店。 这段路的乐趣不只在尽头,台阶上的文字、门前的小装饰和转身后的视野,都值得停下来看看。 雨天石阶较滑,先照顾好脚下,再挑不妨碍通行的位置拍照。

石阶上刻有与谢野晶子《伊香保之街》的诗句
## 关所遗迹

伊香保关所遗迹复原的木造门楼
关所是旧时道路上检查往来人员与物资的设施。 在伊香保看复原的门楼与留番所,可以把注意力放在道路、温泉街和管理制度的关系上,而不必用几句戏剧化的说法概括整个江户时代。

伊香保温泉留番所
参观时结合现场资料看旧道路的位置,会比只记住“关卡”两个字更具体。
## 夏威夷王国公使别邸
0:00
/0:02
1×
伊香保夏威夷王国公使别邸的庭院
这处建筑保存了夏威夷王国驻日公使罗伯特·沃克·欧文曾使用的别邸的一部分。 根据[涩川市的文化财记录](https://www.city.shibukawa.lg.jp/kankou-site/kankou/000373/000375/p000287.html),他于1891年购入这里作为避暑别墅,现存建筑又经历了迁移与修理。 它不是1898年才为公使新建的一整座原貌宅邸。
小小的庭院把温泉街与更远的历史连接起来。 想看相关资料展,请另查[公使别邸的开放说明](https://www.city.shibukawa.lg.jp/kankou/000373/000377/p000266.html)。
## 岸权旅馆的足汤与福一

岸权旅馆在石阶街边设置的足汤
岸权旅馆的“岸权辰之汤”位于第212级台阶一带。 走到这里,可以按现场开放情况稍作休息,最新使用时间见[旅馆的温泉介绍](https://www.kishigon.co.jp/hotspring/)。 街边足汤与住宿设施的浴场不是同一种使用安排。

福一旅馆内的彩色和伞装饰
福一位于石阶街上方。 这里的照片记录了旅馆内的和伞装饰与浴场,不代表所有空间都向路过的游客开放。

福一旅馆的浴场,摄于原旅行记录
福一的大浴场使用黄金汤与白银汤两种泉源。 住宿、日归入浴和包场浴池的条件各有不同,预约前可查[福一的浴场说明](https://www.fukuichi.jp/onsen/),不要把旧照片中的设施名称与现行服务逐项对应。
## 伊香保神社

通往伊香保神社的石阶与鸟居
走到石阶街上方,便来到伊香保神社。 当地把它视为温泉的守护神社,参拜者也会寄托健康与生活的愿望。

伊香保神社
这些是信仰与习俗的表达,不是参拜后必然获得某种效果的承诺。 在这里停一停,再转身看看走过的台阶,也能为上坡这一段画下一个自然的停顿。
## 不如归站与缆车

伊香保缆车的不如归站
缆车连接温泉街的不如归站与山上的见晴站,单程约4分钟。 [涩川市的缆车页面](https://www.city.shibukawa.lg.jp/kankou-site/kankou/000357/000360/p000203.html)列有运行时间和检修停运日,出发前值得再确认一次。

伊香保缆车沿线可眺望的山间云雾
山上的视野取决于天气。 这次照片里有云雾,也有站旁的绣球花,并不是每次来都能看到相同景象。

伊香保缆车站附近的绣球花
0:00
/0:02
1×
缆车站附近绣球花的旅行视频(1/7)
0:00
/0:02
1×
缆车站附近绣球花的旅行视频(2/7)
0:00
/0:02
1×
缆车站附近绣球花的旅行视频(3/7)
0:00
/0:02
1×
缆车站附近绣球花的旅行视频(4/7)
0:00
/0:02
1×
缆车站附近绣球花的旅行视频(5/7)
0:00
/0:02
1×
缆车站附近绣球花的旅行视频(6/7)
0:00
/0:00
1×
缆车站附近绣球花的旅行视频(7/7)
绣球花开时,小径给这一天添了与石阶街不同的颜色。 看过花,再回到温泉街用餐或泡汤,行程可以就此收住,不必再把远处的景点勉强塞进同一天。
### 世界遗产富冈制丝厂之行
URL: https://www.sakashita.page/tomioka-silk-mill/
Last updated: 2026-10-24T13:19:59.000Z
这次去富冈,是为了亲眼看看制丝工厂怎样成为世界遗产。 从车站走到世界遗产中心,再看旧韭冢制丝场与富冈制丝厂,路线不长,却能把蚕茧、机器、建筑和技术传播联系起来。
## 从高崎换乘上信电铁
从东京方向前往,可以先到高崎,再换乘上信电铁,在上州富冈站下车。 到高崎可选择普通列车或新干线,时间与费用各有差别,按当天行程决定即可。

上州富冈站前的小富姑娘吉祥物
我们这次先去站前不远的世界遗产中心,再沿市区前往制丝厂。 若想利用周游小巴,应先查当天的运行路线与班次。
## 先认识蚕茧与生丝
蚕的幼虫吐丝结茧,在茧内化蛹。 制丝利用的是构成茧的长丝,经过煮茧、找绪与缫丝等工序,将多根茧丝合成生丝。
先知道这段过程,再看机器会比较容易理解。 工厂要解决的,是怎样稳定地找到丝头、控制粗细,并持续处理大量蚕茧。 “机械制丝”的意义,也正体现在这些具体工序上。
## 世界遗产中心

群马县立世界遗产中心的建筑

世界遗产中心内的展示空间
群马县立世界遗产中心“セカイト”介绍富冈制丝厂及相关的丝产业遗产。 大屏幕、模型与展板,把养蚕、储藏、制丝和技术传播放在同一条线上。
世界遗产“富冈制丝厂与丝产业遗产群”由四处组成:富冈制丝厂、田岛弥平旧宅、高山社遗迹与荒船风穴。 它们分别涉及不同环节,先看整体介绍,再去工厂,会更容易理解每处遗产的作用。
[群马县立世界遗产中心worldheritage.pref.gunma.jp](https://worldheritage.pref.gunma.jp/whc/)

雨天行驶的市区周游小巴

富冈街头小公园的彩绘拍照板

前往制丝厂途中可以休息的小公园
从车站到制丝厂也可以步行。 途中有小公园和店铺,时间宽裕时,适合把市区散步一起放进行程。
## 旧韭冢制丝场

旧韭冢制丝场的展示设施

介绍韭冢直次郎的画像与书法资料
旧韭冢制丝场由韭冢直次郎于1876年创设,反映了富冈制丝厂之后机械制丝技术向民间传播的一面。 今天可在遗址展示空间里,看见相关人物、史料与地下遗构。

透过玻璃地面观察地下遗构的展示室
它与富冈制丝厂是不同地点,走过去时可以留意两者的规模与用途。 开放和售票服务可能调整,出发前可查富冈市的当前说明。
[富冈市:旧韭冢制丝场www.tomioka-silk.jp](https://www.tomioka-silk.jp/%5Fspot/sightseeing/detail/id%3D7679)
## 富冈制丝厂:官营示范工厂的规模

东置茧所的红砖立面
富冈制丝厂于1872年开始运行,是明治政府为提高生丝品质、传播机械制丝技术而设立的官营示范工厂。 建设引入法国技术,法国制丝专家保罗·布吕纳也参与了设立与指导。
厂址与周边的养蚕基础、用地及水源等条件相关。 这里既有生产建筑,也需要储存蚕茧、安排人员生活和训练技术人才。

制丝厂长长的红砖与木构建筑
参观时,可以观察木骨架与砖墙怎样组合,以及大跨度屋顶如何为生产留下宽阔空间。 我们利用了导览服务,建筑的用途与工序之间的关系,也因此更容易理解。
## 车间里的机器属于哪个时期

缫丝车间内保存的机器与生产空间
创设初期,长约140米的缫丝所设有300个缫丝釜。 但工厂在之后一百多年里持续更换设备,并非所有今天看到的机器都来自1872年。
第二次世界大战后,工厂引入自动缫丝机,直到1987年停止运行。 把早期的法国式技术与后来保存的自动化设备分开看,才能看出制丝技术怎样继续发展。

介绍制丝历史与产业遗产的展厅
来自各地的女工曾在这里学习、工作,并把技术带回其他地区。 展览中的人物与生活资料,也值得和机器一起看,才能理解工业史里实际工作的人。
2014年,这座工厂作为四处系列遗产之一列入世界遗产。 同年,缫丝所、东置茧所与西置茧所三栋建筑被指定为日本国宝。
[富冈制丝厂:历史与建筑www.tomioka-silk.jp](https://www.tomioka-silk.jp/%5Ftomioka-silk-mill/about/)
## 参观之后

礼品店里的蚕茧工艺品与伴手礼

蚕茧造型葛粉点心的包装
礼品店的蚕茧工艺和点心,让这趟工业遗产参观有了轻松的收尾。 这次最有收获的,是把原料、工序与技术传播放在同一条路线里看,红砖建筑也因此不再只是外观。
门票、导览时段、可进入的建筑与保存修理安排,会随日期变化。 出发前先看官方利用说明,再给展览、导览和回站步行留出时间。
[富冈制丝厂:参观与导览www.tomioka-silk.jp](https://www.tomioka-silk.jp/%5Ftomioka-silk-mill/)
### 认识日本舞踊:从动作、音乐与道具看起
URL: https://www.sakashita.page/traditional-japanese-dance/
Last updated: 2026-10-24T13:09:59.000Z
“日本舞踊”是一个特定的表演艺术名称,不等于日本所有舞蹈的总称。 旅行时看到的能乐、舞乐、民俗舞蹈或祭典群舞,各有背景,也不能全部归进同一种技艺。 这里先从日本舞踊的几个观看线索说起。
## 从歌舞伎中发展,也走出歌舞伎舞台
日本舞踊以歌舞伎舞踊的技法为基础,后来逐渐发展为独立的舞台艺术与学习门类。 因此,表演者并不限于歌舞伎演员,也包括不同流派的专业舞蹈家与学习者。 [日本舞踊协会的介绍](https://nihonbuyou.or.jp/pages/about%5Fnihonbuyo/)把舞、踊与所作作为理解它的几个要素,而不是把复杂的历史压成两条互不相干的起源。
## 看身体怎样组织空间
初次观看,不必急着认出每一个动作的名称。 可以先看脚步怎样接近或离开某处,身体重心怎样移动,目光又把观众引向哪里。 有时动作幅度很小,却能让人物之间的距离变得清楚;有时只是一个停顿,也已经带出情绪。
如果作品有故事,先读一段剧情梗概会有帮助。 但表演不一定逐句“翻译”歌词,音乐、动作与角色的关系也可能留有想象空间。
## 扇子、手巾与服装
扇子既可以作为扇子,也能在特定动作里表现其他物件或景象。 手巾、袖子与身体姿态同样参与表达,不是为了让舞台看起来更热闹的附加装饰。 观察同一件道具怎样转换用途,是很容易进入作品的一条线索。
服装与演出方式会随作品、角色和创作而变化。 不能用一套固定的扇骨颜色、腰带样式或服装规则,替所有流派判定表演者的级别。 练习、发表会与正式舞台的准备,也不一定相同。
## 传统与新作都在继续
日本舞踊保留古典曲目,也持续创作新作品。 新作不自动意味着放弃传统,古典也不只是把动作原样重复。 演出中哪些来自既有技法,哪些属于这次作品的组织,可以从节目单与创作者说明里慢慢认识。
想看一场时,先查[协会发布的演出信息](https://nihonbuyou.or.jp/)与主办方公告,再确认会场、票券和摄影规定。 不需要一次掌握全部历史,先记住某个动作怎样与一段音乐相接,就已经是一次具体的观看。
### 日本折纸的历史与文化
URL: https://www.sakashita.page/origami-history-and-culture/
Last updated: 2026-10-24T12:59:59.000Z
一张纸,经过折叠,可以变成鹤、花、小鱼,也可以成为抽象的几何形状。 折纸的乐趣不只在最后的作品,还在于看见一个平面怎样随着折痕慢慢改变。 它不属于某个性别或年龄,也不必从复杂作品开始。
## 先认识山折与谷折
初学时,先练习对齐边角、压出清楚的折痕,再认识山折、谷折等基本动作。 看图解时,要先读懂线条和箭头代表什么,别只凭成品照片猜步骤。 同一张纸翻过来后,方向也会改变,必要时可以把前一步重新展开看看。
正方形折纸容易找到,也适合不少入门作品。 薄而容易折出痕迹的纸更方便练习;等熟悉之后,再按作品需要选择不同大小与质地。
## 礼仪与游戏,两种相连的面貌
日本的折纸传统,既与包裹礼物、礼仪中的纸饰有关,也发展出供人玩赏和练习的形式。 折鹤只是其中广为人知的一种,不能代表全部折纸。 今天的创作还涉及几何、设计和数学,作品可以很简洁,也可以极为复杂。
## 看懂一件作品的做法
在展览或工作坊里,可以留意作品用了几张纸、是否使用剪切或黏合、折痕怎样支撑形状。 这些差异并不影响欣赏,反而有助于看懂制作者选择了什么方法。
如果旅行中遇到体验活动,从一个能在现场完成的小作品开始就很好。 折得稍微不对称也没关系;回家后再照着步骤做一次,往往比只留下成品更能记住那段体验。
### 日本文化中的猫
URL: https://www.sakashita.page/cats-in-japanese-culture/
Last updated: 2026-10-24T12:49:59.000Z
商店门口举着前爪的招财猫、文学作品中的猫、地方铁路的猫站长,让猫以不同方式出现在日本旅行里。 不过,招财猫是寄托吉祥愿望的造型,店里真正生活的“看板猫”则是动物,两者不宜混称。
## 文学里的猫,看见人的生活
夏目漱石的《我是猫》借猫的视角观察人类社会,宫泽贤治的《猫之事务所》也让猫成为故事的重要角色。 这些作品的趣味,不只是角色可爱,更在于动物的视角怎样改变我们对日常关系的观察。 如果喜欢猫主题旅行,先读一篇作品,再看街头的猫造型,也会多一点联想。
## 作为地方象征的猫站长
和歌山电铁贵志川线的初代小玉站长,曾让许多人认识这条地方铁路。 小玉已经离世,后来仍有其他猫站长与相关纪念设施,把这个故事延续下来。 专程到访前,可以查看[和歌山电铁的猫站长与运行信息](https://wakayama-dentetsu.co.jp/),确认当天的安排。
除了猫,列车、车站和沿线街景也值得慢慢看。 把它当作一次铁路小旅行,会比只为碰见某一只猫而来更从容。
## 坡道与岛上的日常
尾道的坡道、窄巷与猫主题装饰,让人容易注意到猫与地方景观的联系。 田代岛等被称为“猫岛”的地方,也仍是居民生活的社区,人口与猫的数量会变化,不能把旧数字当作永久不变的特色。
遇见猫时,不追逐、不擅自抱起,也不因想拍照就把食物拿出来引诱。 先读当地的喂食和参观说明,把居民与动物的生活放在照片之前,才是更舒服的相遇方式。
### 日本夏日祭典的小乐趣:钓悠悠球
URL: https://www.sakashita.page/festival-balloon-fishing/
Last updated: 2026-10-24T12:39:59.000Z
在日本的夏日祭典里,除了看游行和表演,沿街的小摊也很有意思。 水盆里漂着的彩色小气球,就是常见的“钓悠悠球”。 它与用线轴表演花样的悠悠球不同,拿在手里时,可以借橡皮筋让注水的气球上下弹跳。
## 纸线断掉前,试着钓起一只
游戏通常使用系着纸线的小钩子,把水盆中的气球钓起来。 难点在于纸线遇水会逐渐变软:气球的重量、钩的位置和动作的力度,都影响能否顺利提起。 看起来只是简单的一提,真到自己动手时,却很容易紧张。
每个摊位的收费、可尝试次数与带走奖品的规则可能不同。 开始前先问清楚,比纸线断了以后才猜规则更省心。
## 旁边还有哪些小游戏?
捞小弹力球、射击和套圈,也是祭典上常见的摊位游戏。 其中,捞球常用的是蒙着薄纸的网兜,与钓气球的小钩子不同。 不急着参加时,站在不挡路的位置看别人尝试,也能感受到摊位前的热闹。
## 逛摊时,给自己一点停留时间
章鱼小丸子、烤鱿鱼、炒面和巧克力香蕉等小吃,经常出现在祭典现场。 食物看起来方便拿取,也要留意温度、酱汁与周围的人潮,按现场安排找合适的位置吃。
比起把所有摊位都逛遍,挑一两个想玩的游戏,再买一份喜欢的小吃,就足够留下一段具体的祭典记忆。 最后把垃圾带到指定位置,让下一位经过的人也能舒服地逛下去。
### 认识忍者的历史与文化
URL: https://www.sakashita.page/ninja-history-and-culture/
Last updated: 2026-10-24T12:30:00.000Z
黑衣、手里剑、烟雾和飞檐走壁,是影视与漫画中常见的忍者形象。 历史上的“忍”则需要从文献、战争与地方社会中理解。 把两者分开来看,反而更容易发现忍者文化有趣的地方。
## 重要任务是带回情报
历史资料中的忍者活动包括侦察、潜入、引路等,具体任务随时代与雇用者而变。 其中重要的一点,是取得情报并把它带回去,而不是每次都要与敌人正面交战。 [三重大学对忍者研究的介绍](https://www.mie-u.ac.jp/R-navi/interview/cat578/post-6.html),就强调了这种与大众想象不同的活动面貌。
伪装成普通人、观察道路与地形、判断信息是否可靠,都比夸张的“隐身能力”更接近理解这些活动的入口。 不过,也不能把不同年代的记载拼成一份人人都会的技能清单。
## 故事怎样改变了忍者?
随着战争、社会与娱乐形式的变化,忍者逐渐成为小说、戏剧、电影和漫画的角色。 黑色装束、超常能力与神秘武器,能够制造鲜明的视觉效果,也因此不断被后来的作品沿用。 作品里的设定可以很精彩,却不能直接拿来证明历史上的生活与训练方式。
## 参观忍者主题设施时看什么?
可以先分清展出的是真实文献、复原器具,还是为了演示与互动制作的道具。 同一件器物有什么证据、在什么时期使用、说明牌是否明确写着“传说”,都是值得留意的问题。
理解这些层次,并不会减少观看表演或体验展览的乐趣。 它只是让“历史上的忍”与“故事里的忍者”,各自保留应有的位置。
### 阿波舞:德岛的夏日舞蹈与高知鸣子舞节
URL: https://www.sakashita.page/awa-odori/
Last updated: 2026-10-24T12:19:59.000Z
德岛的阿波舞与高知的夜来祭,都能让四国的夏夜热闹起来。 两者有各自的历史、音乐与舞蹈形式,观看时可以从队伍的动作和伴奏听出差别。
## 德岛阿波舞:看队伍怎样合拍
阿波舞以轻快的二拍子节奏为特点,舞者组成称为“连”的队伍,在伴奏中前进。 法被、浴衣与编笠构成了鲜明的服饰印象;常说的男舞与女舞,是观察不同动作风格的入口,实际表演还有多种组合。 队伍大小与组成并不固定,不必把某个人数当成所有“连”的标准。
除了夏季街头的祭典,在德岛的[阿波舞会馆](https://www.awaodori-kaikan.jp/)也能观看公演,认识舞蹈与伴奏。 如果时间不在祭典期间,这是一种更容易安排行程的方式,具体场次请先确认。
## 高知夜来祭:听见鸣子的声音
高知的夜来祭始于1954年,与阿波舞有不同的发展背景。 舞者手中的鸣子会发出清脆的声响,队伍也常在音乐、服装与演出上展现自己的风格。 载着音响等设备的地方车,与后方舞队共同组成街头的表演场面。 关于祭典的起点,可参阅[高知夜来祭官方历史](https://www.cciweb.or.jp/kochi/yosakoiweb/history/)。
## 想参与,先看当年的安排
观看公演、站在沿街观览区域,与加入舞队,是三种不同的体验。 面向初学者的参加方式、集合地点与服装要求,应以各祭典当年的说明为准。 不要把德岛的临时舞队安排直接套用到高知。
## 给夏夜留出返程时间
祭典期间交通与住宿需求较大,先确认观览区域和返程路线,再决定晚餐与停留时间。 德岛与高知位于四国不同地区,如果两处都想看,应分别安排移动,避免以为同在四国就能轻松往返。
### 日本民间故事:金太郎、花开老叟与戴斗笠的地藏
URL: https://www.sakashita.page/the-old-man-who-made-trees-bloom/
Last updated: 2026-10-24T12:10:00.000Z
## 金太郎
从前,有一个男孩和他的母亲居住在足柄山上。 他的名字叫金太郎。 金太郎从小就非常强壮,穿着母亲为他缝制的、绣有“金”字的红色肚兜,经常跑到外面去玩。 山里的兔子、猴子、野猪和熊都是他的朋友。 他们常常一起玩相扑,却没有谁能打倒金太郎。 一只以力气大而自豪的熊也向他发起挑战,结果还是金太郎获胜了。
有一天,金太郎带着短柄斧,骑着熊,和朋友们一起向山里出发。 途中,他们来到悬崖边,发现下面的大河上没有桥。 熊说:“我要推倒一棵树,为大家搭一座桥。” 可是它推了一次又一次,大树依然纹丝不动。
“让我来试一试。” 金太郎说着,用尽全力去推大树。 随着“噼啪”一声巨响,大树倒下,横跨在河面上。 朋友们都欢呼起来。
这时,背后传来一个声音:“好大的力气!” 金太郎转身,看到一位高大的武士和他的随从。 武士说:“你的力量真了不起,何不做我的随从呢?”
“我真的能成为一名勇士吗?” 金太郎问。 武士回答:“当然可以,总有一天,你会成为一名伟大的勇士。”
金太郎回家,把这件事告诉母亲。 母亲说:“我相信你会成为一名出色的勇士。 你的父亲也是一名了不起的勇士。 不要担心我,放心去吧。”
出发前,金太郎与母亲和动物朋友们依依告别。 他向母亲道谢,承诺永远记得养育之恩,将来一定回来接她。
后来,金太郎成为名叫坂田金时的勇士,战胜了住在大江山的食人妖魔。 他回家接来母亲,母子俩从此过上了快乐的生活。
## 花开老叟
从前,日本一个偏僻的山村里,住着一对诚实的老夫妇。 一天,老公公在外耕地,一只白色的小狗哭叫着向他跑来。 小狗正受到邻居贪婪老头的虐待。 老公公把它救回家,给它取名“小白”。
老夫妇非常疼爱小白,小白也对他们忠心耿耿,每天帮助老公公在田里干活。 小狗很快长成了一只大狗。
一天,小白把老公公带到附近的山上,对着一处地面叫起来:“汪汪,挖这里!” 老公公挖开泥土,惊讶地发现里面有闪闪发光的金币。
邻居的贪婪老头听说后,嚷着要借小白,也逼它带自己和妻子上山找金子。 可他们挖出的不是金子,而是垃圾。 老头恼羞成怒,竟然杀死了小白。
老夫妇得知此事,悲伤不已,为小白挖了一座坟。 不久,坟上长出一棵树苗,很快便长成大树。
“小白喜欢米糕,我们做些米糕带到它坟前吧。” 老公公这样想着,把那棵树做成了一个臼。 当他和妻子开始舂米时,臼里的大米竟变成了金币。
贪婪的邻居见到这一幕,又把臼偷回家舂米。 可是他们眼前的大米全变成了黑泥。 老头愤怒地把臼劈成碎片,扔进炉子烧掉。
诚实的老公公十分难过,把臼的灰烬仔细收好,带回家去。 他想把灰撒在田里,种上小白喜欢的萝卜。 撒灰时,一阵风吹过,把灰烬带到旁边的一棵枯树上。 令人惊讶的是,枯树竟开出了繁茂的樱花。 他又把灰撒向其他枯树,树上也都开满了花。
这件事传到大名耳中,老公公被召到面前。 他拿出装着灰烬的盒子,说:“现在就让花儿盛开吧。” 灰烬飘向附近的枯树,转眼间,枝头便开出了洁白的樱花。 大名十分高兴,称赞他是出色的花匠,并给予奖赏。
这时,贪婪的老头拿着自己搜集的残灰闯进来,嚷道:“我才是最伟大的花匠!” 他把灰抛向空中,可树上并没有开花,灰反而飘进大名的眼睛和鼻子里,呛得大名十分难受。 大名震怒,命人把这个贪婪的老头关进了监狱。
## 戴斗笠的地藏菩萨
很久以前,日本一个小乡村里住着一对贫穷的老夫妇。 新年将近,妻子看了看米箱,发现只剩下一点米。 外面积雪很深,他们找不到编斗笠的材料,也就没有斗笠可以拿去卖。
这时,墙洞里传来一只小老鼠喊饿的声音。 老鼠的父母劝它忍一忍,因为这家人也已经没有多少食物了。 老公公听见后很难过,便把最后的大米分给老鼠一家。
第二天早晨,老鼠们走进雪地,带回一大堆可以编斗笠的草,向老夫妇报答赠米之恩。 大家一起动手,很快编好几顶斗笠。 老公公扛起斗笠,冒雪前往市镇,希望卖了钱,买些过年的食物。
到了市镇郊外,他发现路边的地藏石像头上积满了雪。 他取下头巾,轻轻擦去每尊石像上的积雪,然后继续赶路。
新年前夕的街道热闹极了。 老公公不停吆喝:“斗笠,谁需要斗笠?” 可是一直到夜色降临,也没有人买他的斗笠。 他只好扛着货物,踏上回家的路。
路过郊外时,他发现地藏石像头上又积起了厚雪。 他再次擦去积雪,说:“一顶斗笠都没卖掉,也没钱买供品。 就把这些斗笠送给你们吧。”
他为石像一一戴上斗笠,却发现有六尊石像,斗笠只有五顶。 于是,他把自己的头巾轻轻放在第六尊石像头上,然后两手空空地回家了。
妻子和老鼠们起初以为斗笠全卖完了,听完经过后,妻子温柔地说:“这是好事。 我们就用腌菜和温水迎接新年吧。”
午夜时分,门外忽然传来呼喊:“新年送货! 卖斗笠老公公的家在哪里?” 令人惊讶的是,来者正是那些地藏石像。 他们拉着雪橇,带来大米、豆酱和许多好吃的食物。
“谢谢你的斗笠,这些礼物是我们的回报。 新年快乐!” 留下礼物后,地藏石像便回到了郊外。
老夫妇有了足够的食物,便请老鼠邀请动物朋友们一起过年。 他们还准备了米糕和过年的食物,送到地藏石像前,表达感谢。 老公公回到家,与妻子、老鼠和动物朋友们迎来了一个欢乐的新年。
### 日本的节假日与传统活动
URL: https://www.sakashita.page/japanese-holidays-and-traditions/
Last updated: 2026-10-24T12:00:00.000Z
日本的“国民祝日”、传统节日与学校或公司的休假,并不是同一张日历。 女儿节、七夕与盂兰盆节都有鲜明的季节感,但不会自动成为全国统一放假的日子。 旅行时先分清这几类,比较容易理解营业、交通与活动安排。
## 先看法定祝日
日本目前有16个国民祝日。 日期固定的节日,与按某月某个星期一安排的节日并存,春分和秋分则要查当年的官方日历。
| 时段 | 主要国民祝日 |
| ------- | ----------------- |
| 1月 | 元旦、成人日 |
| 2月 | 建国纪念日、天皇诞生日 |
| 3月 | 春分日 |
| 4月至5月 | 昭和日、宪法纪念日、绿之日、儿童日 |
| 7月至8月 | 海之日、山之日 |
| 9月 | 敬老日、秋分日 |
| 10月至11月 | 体育日、文化日、勤劳感谢日 |
补休日与夹在祝日之间的休假,也会改变实际连休的长度。 预订交通和住宿时,应直接查出发年份的日期。
[日本内阁府:国民祝日与年度日历www8.cao.go.jp](https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html)
## 除夕与新年
12月31日,人们会吃跨年荞麦面、看电视节目,或听寺院的除夜钟声。 各家的过法不同,街上的人流与营业安排也会和平日有明显差别。
元旦是1月1日。 门松、新年料理、杂煮与新年第一次参拜,都是常见的习俗。 新年初次参拜可以前往神社,也可以去寺院,日期不一定都集中在元旦当天。
年末年初的休假,通常比单独一个元旦更长。 小店、博物馆和诊所等设施可能连休,旅行时最好提前备好要用的信息。
## 成人日与成年年龄
成人日是1月第二个星期一。 许多地方在这段时间为满20岁的年轻人举行纪念仪式,也有地方因气候或其他安排改在不同季节。
日本民法上的成年年龄,已于2022年4月调整为18岁。 成人仪式的对象与成年年龄可以不同,饮酒与吸烟的年龄限制仍是20岁。 看到年轻人穿振袖或礼服参加仪式时,可以把法律规定与地方庆祝方式分别理解。
[日本法务省:成年年龄相关说明www.moj.go.jp](https://www.moj.go.jp/MINJI/minji07%5F00238.html)
## 节分:撒豆与迎春

节分的鬼面包装与福豆
节分通常指立春前一天,因此日期并非永远固定在2月3日。 撒豆时喊“鬼出去,福进来”,是在新季节到来前祈愿除厄迎福的习俗。
有些地方使用炒黄豆,有些使用带壳花生;吃豆的数量与撒豆方式,也有地方和家庭差异。 柊树枝与沙丁鱼头、面向当年吉方吃惠方卷等做法,同样应放在民俗与祈愿中理解。
神社与寺院的撒豆活动有各自的时间、参加方式和拍照规定。 想现场观看时,先查主办方公告即可。
## 女儿节与儿童日
3月3日的女儿节,常见陈设是雏人形。 它寄托女孩健康成长的愿望,通常不是全国法定假日。 更多背景可以看[日本的女孩节](https://www.sakashita.page/hinamatsuri-doll-festival/)。

端午时节悬挂的鲤鱼旗
5月5日则是法定的儿童日,也承接了端午的传统。 鲤鱼旗、盔甲与武士人偶等陈设,常与男孩的成长祈愿联系在一起;儿童日的法定意义则面向儿童整体。
## 春秋彼岸与黄金周
以春分或秋分为中心,前后各三天、合计七天的期间称为彼岸。 扫墓、供花与思念先人,是这一时期常见的活动。 只有相应的春分日、秋分日本身属于国民祝日,七天并不会全部放假。
黄金周一般从4月末延伸至5月初,连接昭和日、宪法纪念日、绿之日与儿童日等祝日。 具体能连休多少天,要看星期分布和个人休假。 热门交通与住宿容易集中预订,行程越明确,越适合提早确认。
## 梅雨与七夕
梅雨是季节气候,不是节日。 日本各地的开始与结束时间不同,北海道通常不属于气象部门发布梅雨起止的区域,但仍可能遇到持续阴雨。

七夕时系在竹枝上的心愿短笺
7月7日的七夕,与牛郎织女传说及其他习俗相结合。 人们会把愿望写在短笺上挂到竹枝,也有地方在8月或依旧历举行相关活动。 各地祭典的日期,不能仅凭“七夕”这个名字推定。
## 盂兰盆节与夏季休假
盂兰盆节与迎接祖先、祭祖和追思有关。 有的地方在7月举行,有的在8月,依旧历安排的地区也保留了不同日期。
盆舞是这一季节常见的活动,但抬神轿的夏祭与佛教盂兰盆习俗,各有自己的来历。 参与活动前,可以先看当地的介绍和参加规则。
盂兰盆期间,返乡与旅行的人流往往集中。 它本身并不是全国统一的国民祝日,公司、学校及商店是否休息,各有安排。
## 土用丑日与七五三
土用是季节转换前的一段历法期间,一年有不止一次。 今天最广为人知的,是夏季土用丑日吃鳗鱼的习俗,具体日期应查当年日历。

七五三时身穿和服的儿童插图
七五三以11月15日为传统日期,为三岁、五岁、七岁儿童的成长祈愿。 不同年龄与性别的庆祝方式有地方差异,许多家庭也会选附近的周末参拜、拍照。 千岁糖是其中常见的节庆物品。
## 秋天的红叶狩

红叶季节的庭园与人偶装饰
“红叶狩”指欣赏秋叶。 它没有统一的放假日期,赏叶时间则随纬度、海拔和当年天气变化。 同一趟旅行中,山区与市区可能呈现不同进度。
把节日当作认识生活的入口,会比只记日期更有意思。 出发前再核对交通、开放日与活动公告,就能把想体验的季节风景放进行程。
### 认识日本的柏青哥
URL: https://www.sakashita.page/pachinko-basics/
Last updated: 2026-10-24T11:49:59.000Z
在日本街头,常能看到招牌醒目的柏青哥店。 对第一次来日本的游客来说,店内密集的机器、灯光和声音很容易引起好奇。 柏青哥究竟怎么玩,又和游戏厅有什么不同?
## 钢珠怎样移动?
柏青哥是一种使用小钢珠的游戏。 玩家操作机器右下方的把手,将钢珠送入游戏盘面;钢珠在钉子之间弹跳、落下,进入特定的入赏口后,机器会按规则给出相应结果。 许多机台还结合了屏幕动画、音效和主题演出。
它看起来像弹珠台,但操作方式不同:玩家主要调节发射的力度,不能在钢珠落下时用挡板把球弹回去。 盘面的路径与屏幕上的演出共同构成了游玩的节奏。
## 从街头认识一种娱乐文化
不同店铺和机型的规则并不完全相同,游戏结束后的奖品兑换也应以店内说明为准。 如果只是好奇,了解这种娱乐形式和店面文化就足够了,不必为了“体验日本”而勉强参与。
真要游玩时,先看懂操作说明,给时间和花费设定上限。 店内的声音与灯光往往较强,是否适合停留,也可以按自己的感受决定。
### 宇都宫一日游:城址、石造教堂与饺子
URL: https://www.sakashita.page/utsunomiya-city-walk/
Last updated: 2026-10-24T11:39:59.000Z
宇都宫是栃木县的县厅所在地,从东京乘新干线就能到达。 这次先走车站西侧的市中心,看城址、石造教堂和神社,吃过饺子后,再去车站东侧试乘LRT。 两部分的交通方向不同,分开安排会更顺路。
## 用黄鲫小巴串起市中心
当天的市中心路线是:JR宇都宫站、城址公园、松之峰教会、县厅展望台、二荒山神社,再到饺子通。 步行之外,也可以用市内循环巴士“黄鲫”衔接部分路段。
[宇都宫市内循环巴士“黄鲫”www.city.utsunomiya.lg.jp](https://www.city.utsunomiya.lg.jp/kurashi/kotsu/kokyo/1006069.html)
班次和车票种类请在出发前确认,短距离的景点也可以直接步行。 先看清返程方向,比到了站牌才查下一班车更从容。
### 城址公园收到的一条小鱼

城址公园工作人员赠送的黄鲫折纸
在城址公园,工作人员送了我们一条黄鲫折纸。 这条黄色小鱼是宇都宫的传统吉祥物,与祈愿无病息灾的地方传说相连。 今天还能在巴士车身和当地小物上看见它,旅行中一旦留意起来,就会不断遇到。
## 宇都宫城址公园
今天的城址公园展示了部分复原的城郭设施,包括清明台、富士见橹、土墙、土垒和护城河。 现有复原区于2007年开放,可以从城楼、步道与周围街区的关系,想象昔日城郭的尺度。

土垒上的复原清明台
清明台是两层的城橹,曾承担类似天守的作用。 江户时代,宇都宫也是将军前往日光途中停留的重要地点。

通往复原富士见橹的公园步道
普通开放日可参观城橹的一层,上层特别公开等安排另行公布。 走上土垒后,能把白墙、草坡和城市建筑放在同一幅画面中。
[宇都宫城址公园的设施与开放信息www.city.utsunomiya.lg.jp](https://www.city.utsunomiya.lg.jp/citypromotion/kanko/meisho/jyiousi/1007276.html)
## 松之峰教会:大谷石的表情

松之峰天主教堂大谷石外墙前盛开的绣球花
松之峰教会的现有圣堂建成于1932年,采用罗马式复兴风格。 双塔、拱窗和大谷石外墙,让它在市中心很容易辨认。 建筑于1998年登记为日本国家有形文化财。
大谷石是宇都宫一带具有代表性的石材,表面的纹理和孔隙值得近看。 初夏时,墙前的绣球花又为石造建筑添上一层柔和的颜色。
教堂仍是举行礼拜的场所,进入时请按现场开放与拍摄说明参观。 [宇都宫官方教会介绍](https://www.utsunomiya-cvb.org/spot/detail%5F10006.html)提供位置与参观信息。
## 县厅:草莓与高处的城市风景

县厅院子里的草莓雕塑
来到县厅,院子里的白色草莓雕塑先吸引了视线。 栃木县以草莓种植闻名,在县厅也能从展示、装饰和拍照点感受到这个主题。

县厅内的巨型红色草莓拍照装置

旅行时看到的盒装白草莓
县厅本馆15层设有展望大厅,可眺望市区。 天气通透时,还能寻找远处的山影;傍晚则适合看街区逐渐亮起灯光。

草莓图案的电梯
展望空间偶尔会用于活动,整栋楼也有闭馆日。 计划看夜景时,先查[县厅公共参观空间的开放说明](https://www.pref.tochigi.lg.jp/c05/kensei/chousha/kenmin.html),再安排晚餐和返程。
## 宇都宫二荒山神社

宇都宫二荒山神社的社殿与紫色帷幔
二荒山神社位于市中心的高处,是当地人熟悉的参拜地点。 从街边走上石阶,周围的声音渐渐远去,也让热闹街区里的散步有了一段安静的停顿。
参拜时依照现场指引行动即可。 如果手水舍开放,按设施说明使用;礼拜或祭典进行时,给参拜者留出空间。
## 饺子通:先看开店时间,再决定吃哪家

宇都宫站西口“饺子像”
宇都宫以饺子闻名,JR站西口的“饺子像”也是城市的标志之一。 这座大谷石雕塑把维纳斯与饺子的形象结合在一起,出站时可以顺路看看。
二荒山神社附近的宫岛町通也被称为“饺子通”,沿街能找到不同风格的店。 煎饺、水饺和炸饺可以按胃口选择,不必一口气安排太多家。

这次吃过黑毛和牛水饺的CAROL店铺外观
我们到得偏早,还等了一会儿才开店。 这次在CAROL尝到的黑毛和牛水饺很合口味,也提醒我,吃饭的时间最好提前放进行程里。

宇都宫饺子通上的饺子图案井盖
除了店铺,路上的饺子图案井盖也很有趣。 吃过饭再沿街找一找这些小细节,散步会轻松许多。
## 换到东口:试乘LRT

宇都宫站东口广场与LRT站台

停靠在站台的黄色宇都宫LRT列车
芳贺·宇都宫LRT于2023年8月26日开通,从JR宇都宫站东口向东行驶。 它与这次西侧市中心的路线分属不同方向,适合吃完饭回到车站后,再另安排一段体验。

LRT站台上的乘车整理券发券机
这次试乘到了“宇都宫大学阳东校区”站,印象是车内安静,行驶也平稳。 用交通IC卡乘车时,上下车分别刷卡;现金乘客则先在站台领取整理券,下车时到前方运费箱付款。
[宇都宫LRT的乘车方法www.miyarail.co.jp](https://www.miyarail.co.jp/userguide-notes)

黄鲫小巴
## 一天的节奏
- 🚶上午:从JR宇都宫站西口进入市中心,串联城址公园和松之峰教会。
- 中午至下午:按开店时间吃饺子,再到二荒山神社或县厅展望大厅。
- 🚋如果还有时间:回到车站东口,选一段LRT试乘,再返回车站。
想细看建筑、慢慢吃饭的话,市中心本身就能安排一天。 把LRT作为可选的一段,行程会更有余地。
### 角馆的桦细工:樱树皮里的手工之美
URL: https://www.sakashita.page/cherry-bark-craft/
Last updated: 2026-10-24T11:29:59.000Z
角馆的桦细工,名字里虽然有“桦”,主要使用的却是山樱树皮。 深浅不一的棕色、细密的横纹和经过打磨的光泽,使茶筒、小盒等日用品呈现出温润的质感。
## 从武士的手艺到日常器物
根据[仙北市对桦细工历史的介绍](https://www.city.semboku.akita.jp/sightseeing/densyo/rekishi.html),天明年间,角馆武士藤村彦六从秋田北部的御处野家学得技艺,并将其带到角馆。 早期制品包括印笼、随身用具的收纳盒等;后来,茶筒等生活器物成为人们熟悉的代表。 桦细工于1976年被指定为日本的传统工艺品,角馆桦细工传承馆于1978年开馆。
## 看什么,怎样看?
观察桦细工,可以先看树皮本身的纹理,再看接缝、边缘和盖子的处理。 自然材料的颜色并不完全一致,这些差异也是每件器物的特点。 相比只看一个“传统工艺”的标签,留意这些细节更能理解制作所需的耐心。
在角馆散步时,可把桦细工传承馆与武家屋敷街区安排在同一段行程里。 先看街道和住宅,再看与当地生活相连的器物,城下町的历史便多了一层具体的触感。 展览、制作演示和商店的安排可能调整,参观前可查看[仙北市的传承馆信息](https://www.city.semboku.akita.jp/sightseeing/densyo/)。
### 长崎旅游指南
URL: https://www.sakashita.page/nagasaki-travel-guide/
Last updated: 2026-10-24T11:19:59.000Z
长崎市的旅行,可以沿着港口、坡道与路面电车展开。 贸易、移居、宗教与战争留下了不同层次的记忆,散布在市区的建筑与街道里。 第一次到访,不妨先选一两个主题,再用交通把它们串起来。
## 港口与异文化的历史
长崎与葡萄牙、荷兰、中国等地的交流发生在不同历史阶段,贸易与人员往来的形式也随之改变。 出岛、中华街、东山手与南山手,提供了理解这些联系的不同入口。 参观时留意建筑年代和使用者,比把它们统称为“异国风情”更具体。
东山手、南山手有旧外国人居留地的历史,今天散步时仍能看到相关建筑与说明。 大浦天主堂和周边建筑,也应放在各自的宗教与历史背景中认识。 长崎孔子庙则让人从另一条线索认识当地华侨社会与中国文化的联系。
## 记住1945年8月9日
和平公园、原爆落下中心地与长崎原爆资料馆彼此相关,却各有不同的空间与展示。 与热闹的港口散步相比,这一段适合放慢节奏,认真阅读资料和受害者的记述。 不要把纪念场所只当作拍照打卡点。
## 山上的风景与桌上的味道
稻佐山是眺望长崎港与城市夜景的代表性地点。 是否看得清楚受天气影响,日落前后到达,可以观察光线的变化;返程交通也要提前确认。
饮食方面,可以从长崎什锦面、皿乌冬、蜂蜜蛋糕和卓袱料理等地方特色认识交流的痕迹。 蜂蜜蛋糕与葡萄牙点心的传播有关,一块切面细密的蛋糕,也能把交流的历史带到餐桌上。
## 市内移动,先看地形
路面电车适合连接不少市区景点,坡地与台阶则需要额外的步行时间。 一天里想兼顾历史街区、和平主题和夜景,最好减少临时绕路,给吃饭和休息留一点空间。 设施与交通可从[长崎市官方观光网站](https://www.at-nagasaki.jp/)进一步查询。
### 熊本市内一日游
URL: https://www.sakashita.page/kumamoto-day-trip/
Last updated: 2026-10-24T11:09:59.000Z
熊本市内的一天,可以在城郭、庭园与商业街之间取舍。 熊本城和中心街区适合连在一起走,水前寺成趣园则需要另搭一段市电。 把地图上的距离与看展时间算进去,比从早到晚塞满六个固定时段更实用。
## 一条可调整的路线
1. 🚶 上午先看熊本城与当前开放的参观通道。
2. 午餐后留在中心街区,逛美术馆、上通或下通。
3. 🚋 若更想看庭园,改乘市电去水前寺,减少下午的购物与看展。
4. 傍晚回到市区用餐,再按住宿或车站位置返程。
这是两种下午安排,不是要求同一天全部完成的清单。 喜欢城郭的人可以多留时间看修复,喜欢园林的人则把美术馆留给下次。
## 熊本城:看见复旧中的城
熊本城的历史与加藤清正、细川氏密切相关,2016年地震又给城郭带来严重损伤。 天守完成复旧并重新开放,不意味着全部橹、石垣与御殿都已恢复到同一种参观状态。
例如宇土橹仍涉及解体保存与修复,不能把旧介绍中的“可以进去看”直接沿用。 出发前请对照[熊本城的复旧状况](https://castle.kumamoto-guide.jp/map/)与当日观览说明,选择开放路线。 修复过程本身也能帮助理解城郭怎样由不同建筑、地基和石垣组成。
## 水前寺成趣园:从池边换一种步调
水前寺成趣园是围绕池水组织景观的回游式庭园,与细川氏的历史相关。 池、人工山丘与树木之间的构图,适合沿路逐段看,不需要把它当成一张必须站在正中央拍完的照片。
园方在[官网](https://www.suizenji.or.jp/)介绍了庭园沿革、地图与交通。 乘市电时在水前寺公园一带下车后,还需步行入园;留好这段路和返回市区的时间。
## 现代美术馆与中心商业街
熊本市现代美术馆位于市中心,适合与上通、下通一带组合。 先看[美术馆当前展览](https://www.camk.or.jp/),再决定要不要安排完整的观展时段。 并非每次到访都会看到同一批作品,也不是经过门口就等于完成一次参观。
商店街则适合午后找咖啡馆、看店或挑选土产。 不用把上通与下通各分配一段严格相同的时长,沿着自己感兴趣的店铺走,会更像真正的城市散步。
## 晚餐吃什么
可以按口味选择熊本拉面、辛子莲藕或其他当地料理。 熊本拉面有自己的地方特点,不能把所有知名连锁拉面都当成熊本风格的代表。 具体背景另见[熊本拉面入门](https://www.sakashita.page/kumamoto-ramen/)。
一天只需要有两三个想认真停留的地方。 看城、走庭园或逛街,选好自己的重点,熊本市区就不必变成一场从早到晚的赶路。
### 熊本的拉面
URL: https://www.sakashita.page/kumamoto-ramen/
Last updated: 2026-10-24T10:59:59.000Z
熊本拉面的辨识度,往往先来自一阵焦香的蒜味。 豚骨汤、中粗直面,以及炒香或炸香的蒜片、蒜油,是认识这种地方拉面的几个线索。 不过,每家店的配方不同,不能把一种味道当成所有店铺的统一标准。
## 从汤、面与蒜香开始
根据[熊本市观光指南的介绍](https://kumamoto-guide.jp/spots/detail/258),熊本拉面与久留米、玉名一带的豚骨拉面发展有关。 部分汤底还会加入鸡骨等材料,搭配木耳、叉烧和葱等配料。 常被提到的“マー油”是带有焦香的蒜油,不是发酵黑蒜制成的油。
吃第一口时,可以先尝汤,再留意面条的粗细、弹性与蒜香的关系。 同样写着“熊本拉面”,浓淡、油量与蒜味的强弱也可能差很多。
## 玉名拉面不是一家店
玉名是熊本县内的城市,“玉名拉面”指当地的拉面风格,不能与黑亭、味千等店名或品牌并列成同一种分类。 [玉名市的观光资料](https://tamana-tamatebako.net/tamanomeshi/tamanomeshi.html)也把当地拉面与焦香蒜片联系起来。 如果行程经过玉名,可以把它作为认识熊本县拉面文化的另一站。
## 安排一顿拉面
选店时,与其追逐“必吃”名单,不如先看位置、营业时间和自己的口味。 有些店在午餐与晚餐之间休息,热门时段也可能排队。 菜单、营业与配料以店方说明为准;食材有特殊要求时,点餐前先确认。
### 鹿儿岛名园:仙岩园
URL: https://www.sakashita.page/senganen-garden/
Last updated: 2026-10-24T10:49:59.000Z
仙岩园是鹿儿岛市内面向锦江湾的岛津家别邸庭园。 在这里看园林,视线并不止于围墙与树木:海湾与樱岛也是景观的重要部分。 天气清朗时,远处的火山与近处的庭园能形成很有层次的画面。
## 岛津家的别邸
根据[仙岩园对御殿的介绍](https://www.senganen.jp/how-to-walk/the-house/),庭园由岛津家第19代当主光久于1658年营建。 这里的世代从岛津家谱系计算,也反映了这座庭园与家族历史的关系。 后来的整备与建筑改修,使庭园成为理解岛津家生活与地方历史的一处入口。
## 把樱岛与锦江湾纳入视线
借景是仙岩园的看点之一。 散步时可以换几个位置,观察树木、建筑、海面与樱岛怎样进入同一个画面。 山景是否清楚会受天气影响,阴天也可以把注意力放在近处的石、植物和建筑细节上。
御殿有独立的参观内容与使用方案,不能把普通庭园入场和全部室内空间视为同一张票的当然范围。 如果想细看建筑,预订或购票前先确认所选方案。
## 庭园之外,还有工业史
周边的尚古集成馆等设施,把视线从庭园生活延伸到萨摩的近代工业。 若对这一主题有兴趣,可以预留额外时间,而不是把所有内容压进一次短暂停留。
知览武家屋敷与樱岛也是鹿儿岛旅行的重要目的地,但都不是仙岩园内的景点。 安排当天路线时,应分别计算交通与参观时间。 园内路线、开放与交通可查看[仙岩园官方网站](https://www.senganen.jp/)。
### 认识江户时代
URL: https://www.sakashita.page/edo-period-notes/
Last updated: 2026-10-24T10:39:59.000Z
江户时代通常指1603年至1868年德川幕府统治的时期。 旅行时常听见的城下町、参勤交代、长屋和“三都”,都与这个时代有关。 把这些词放回城市与生活的背景里,许多街景和展品会更容易理解。
## 江户:将军所在的城市
江户是德川幕府的所在地,也就是今天的东京。 将军、大名及其家臣的活动,带来了住宅、道路和物资供给的需求。 武家府邸、町人的店铺与住宅、寺社等,共同构成了城市空间。
在博物馆里看江户模型,可以留意一条街上工作与居住怎样结合,或一座城与周围道路怎样相连。 这样比只记住“政治中心”四个字,更能理解城市的运作。
## 京都:朝廷、工艺与文化
幕府设在江户,并不意味着京都失去了作用。 天皇与朝廷仍在京都,寺社、工艺、学术与出版等活动也在这里延续。 观察织物、器物或祭典时,可以留意它们与京都长期积累的技艺和社会网络之间的联系。
## 大阪:物资流通与商业
大阪以水运和商业活动著称,常被称为“天下的厨房”。 各藩在大阪设置的“藏屋敷”,是保管和销售年贡米、地方物产的据点,不能简单理解为商人自行建立的一般仓库。 河道、桥梁与仓储的关系,是阅读大阪历史景观的一个切入点。
今天的大阪美食不能全部倒推为江户时代的传统。 例如章鱼烧是近代出现的食物,介绍江户城市文化时应避免把不同时代混在一起。
## 带着问题去参观
“江户重政治、京都重文化、大阪重商业”适合入门,却只是简化的概括。 三座城市都有人居住、生产、交易与娱乐,也不断彼此影响。 参观时多问一句“谁在这里生活,东西怎样来到这里”,会比套用一个城市标签更有收获。
### 日本温泉的入浴方法与礼仪
URL: https://www.sakashita.page/onsen-bathing-etiquette/
Last updated: 2026-10-24T10:29:59.000Z
第一次去日本温泉,最容易困惑的往往不是怎样泡得久,而是从更衣室到浴池应该怎样做。 先看清设施说明,再按自己的节奏入浴,就能少一些手忙脚乱。
## 进浴场前先确认
男女浴场、家族浴池和需穿泳衣的设施,使用方式并不相同。 确认入口、更衣空间、毛巾与洗浴用品的安排;纹身、拍照和儿童入场等规定,也应以所选设施为准。 一般浴场内不要拿出手机拍照,更不要拍到他人。
## 洗净身体,再入浴
先在洗浴区清洗身体,把肥皂和洗发水冲净,再进入共用浴池。 坐着淋浴时,留意水不要喷到旁人身上,用完后也把座位和盆子简单冲洗干净。
用少量温水逐渐适应温度,再慢慢进入浴池。 毛巾和长头发不要浸进池水,浴池也不是游泳或洗衣的地方。 想聊天可以轻声说话,给其他人留出安静休息的空间。
## 时间与水温,别勉强
泉质、水温和身体情况各不相同,不必为了“泡够本”而追求统一的分钟数。 感到不适时应停止入浴,并请工作人员协助。 饮酒后或身体不舒服时,不要勉强泡汤;有健康方面的顾虑,应先咨询医生。
入浴前后注意补充水分,起身与离开浴池时慢一些。 日本环境省提供了[温泉使用注意事项的多语言说明](https://www.env.go.jp/nature/onsen/pdf/hyouki2021.pdf),现场的温泉分析表与使用提醒也值得读一读。
## 离开前的小动作
进入更衣室前,先擦掉身上多余的水,保持地面干爽。 泡汤后留一点休息时间,再继续散步或赶车。 把共用空间照顾好,也是温泉体验的一部分。
### 探访日光的世界遗产
URL: https://www.sakashita.page/nikko-world-heritage/
Last updated: 2026-10-24T10:19:59.000Z
这次初夏去日光,我们从浅草乘SPACIA X出发,抵达的是东武日光站。 它与JR日光站是不同车站,安排饭店接驳时,要先说清到站地点。
住宿选在神桥附近的金谷饭店,再步行游览轮王寺、东照宫与二荒山神社。 几个地点相邻,但每一处都有自己的参观范围与售票安排,想认真看建筑,一天很容易就走满。
## 世界遗产“日光的社寺”
1999年,“日光的社寺”列入联合国教科文组织世界遗产名录。 范围包括东照宫、二荒山神社与轮王寺的建筑及其周边环境。 它们共同形成一片宗教与历史景观,各自又有不同的祭祀、建筑与沿革。
[联合国教科文组织:日光的社寺whc.unesco.org](https://whc.unesco.org/en/list/913/)
## 日光金谷饭店:先从住宿认识近代日光

日光金谷饭店的历史建筑与花园
金谷善一郎于1873年开设前身“金谷Cottage Inn”,1893年迁至现在的位置发展为金谷饭店。 把创业年份与当前建筑的发展分开看,才不会让“150多年历史”变成一座建筑始终未变的印象。

饭店大厅的木雕装饰与回转门
回转门、木雕与室内装饰,是这次住宿里很值得看的细节。 饭店今天仍在营业,欣赏建筑时,也应配合可进入的公共区域,不打扰其他客人。
[金谷饭店:创业与建筑的历史www.kanayahotel.co.jp](https://www.kanayahotel.co.jp/150th/)
## 神桥与参道入口

大谷川上的朱红色神桥
神桥属于二荒山神社,与胜道上人渡河及神灵化桥的传说有关。 传说为景观增加了故事背景,眼前的桥本身则经历过不同时期的重建与保存。
如果想上桥参观,应另查开放时间和费用;前往社寺的道路也有自己的行人通道。 在河边看桥与水流时,记得给往来车辆和行人留下空间。

通往日光社寺区域的参道入口
跨过道路进入参道,街区的声音渐渐被杉林与石阶取代。 从饭店步行到这里,让这段旅行很自然地从近代旅馆走入更早的宗教空间。
## 轮王寺与逍遥园
轮王寺的历史与胜道上人开创日光山有关。 三佛堂供奉阿弥陀如来、千手观音与马头观音,是认识寺院信仰的重要空间。

轮王寺三佛堂的外观

轮王寺逍遥园入口与石碑
逍遥园起源于江户时代的庭园,之后多次改修,明治时期又有调整,逐渐形成今天的景观。 园方把小堀远州作庭的说法作为一种传承介绍,不能直接把现状全部归为一人一次完成。
轮王寺还包括与第三代将军德川家光相关的大猷院。 三佛堂、宝物殿与庭园、大猷院各有参观内容,购票时先看组合与范围,再安排步行顺序。
[轮王寺:各区域与开放安排www.rinnoji.or.jp](https://www.rinnoji.or.jp/)
## 东照宫:从整体看向细节

东照宫入口的石碑与参道
东照宫奉祀德川家康,与其“东照大权现”的神号相连。 1617年创设后,在德川家光时代进行大规模营建,形成今天广为人知的华丽建筑群。

阳明门上密集而精细的雕刻
阳明门适合先从远处看整体,再近看柱、栏与屋檐之间的雕刻。 人物、动物和想象中的灵兽层层排列,我们原本预留了90分钟,仍觉得有许多细节没看完。

阳明门柱上的纹样与“逆柱”细节
有一根柱子的纹样方向与其他柱子不同,被称为“逆柱”。 有关它的解释,属于建筑意匠与避邪观念相连的传统说法,可以作为观察纹样的一个入口。

杉林旁的东照宫五重塔
五重塔最初于1650年奉纳,后来遭遇火灾,现有建筑为1818年再建。 同一片社寺里,创设、重建与修复的年代并不相同,这也正是看建筑时值得留意的部分。
### 三猿与眠猫

神厩舍上的三猿雕刻
神厩舍的猴子雕刻,常被解释为借猴子的一生表现人生。 其中最熟悉的三猿,是遮眼、遮耳与遮口的形象,通常对应“不看、不听、不说”的教诫。

东回廊檐下的眠猫雕刻
眠猫是另一处很受关注的细节,周围也流传着关于和平与警戒的解释。 看雕刻时,可以先观察实际的姿态、位置与周围图像,再听导览怎样介绍它们。
[日光东照宫官方网站toshogu.jp](https://toshogu.jp/)
## 二荒山神社与杉林

杉林中的朱红色楼门
二荒山神社与日光山岳信仰关系密切。 从东照宫一带步行过去,朱红建筑、杉林与参道的气氛又有所变化。

参道旁排列的石灯笼
有关“化灯笼”的故事,对应的是境内特定的铜灯笼,不能把参道旁所有石灯笼都混为一谈。 参观时若对此感兴趣,可以找现场的由绪说明,看看传说具体指向哪一件器物。
## 安排行程的余量
- 🚃 从浅草出发:选择前往东武日光的列车,另确认特急券与座位。
- 🚶 社寺区域:按入口、售票与最后入场时间安排,石阶和坡道也要算进体力。
- 🚌 继续去中禅寺湖:另外查看山路公交、住宿与季节交通,留出独立的一段时间。
这次走过社寺之后,我们还计划继续前往湖边。 把山上与社寺区域分成两段,才能给建筑细节与自然景色都留一点时间。
### 青森县立美术馆与三内丸山遗址
URL: https://www.sakashita.page/aomori-art-and-jomon-history/
Last updated: 2026-10-24T10:09:59.000Z
青森县立美术馆与三内丸山遗址相距不远,适合放在同一天理解。 一个从艺术作品与建筑展开,一个从考古材料与聚落遗迹展开。 它们之间的联系不只是“两个景点顺路”,也在于怎样通过留下来的形状,认识曾在这里生活、创作的人。
## 美术馆:先选想看的作品
青森县立美术馆收藏栋方志功、奈良美智、成田亨等与当地相关艺术家的作品。 [馆方的收藏介绍](https://www.aomori-museum.jp/collection/)也提供艺术家的经历与创作背景。 成田亨的创作涉及雕塑与特摄美术设计,并不只是一张“怪兽画家”的标签。
收藏介绍与当期展览不能等同。 馆方会按主题轮换作品,因此行前最好先选出自己在意的艺术家,再看当前展览,而不是假设每件代表作都在原位等待。
夏加尔为芭蕾《阿烈科》创作的舞台背景画,是另一种观看尺度。 其中三幅由青森方面收藏,第三幕背景画借自费城艺术博物馆;馆方在2024年公布,借用期延长至2027年3月底。 [这份借展公告](https://www.aomori-museum.jp/article/13572/)说明了四幅作品聚在一起的条件,也提醒我们留意展览的时间边界。

青森县立美术馆外的展览标识

青森县立美术馆的白色建筑与庭院
白色建筑、下沉空间与室外路径本身也值得慢慢走。 参观奈良美智的“青森犬”时,请另查[馆内与室外通道说明](https://www.aomori-museum.jp/visit/?target=floor),不要把从馆内看作品和经室外通道接近作品的路线当成同一个入口。
## 三内丸山:考古遗址与复原建筑

三内丸山遗址入口的石碑
三内丸山遗址主要属于距今约5900至4200年前的绳文时代聚落。 它不是一座完整保存至今的古村,而是通过发掘发现房屋、墓葬、道路等遗迹,再结合出土材料进行展示与复原的遗址。
2021年,包含三内丸山在内的“北海道及北东北的绳文遗址群”列入世界文化遗产。 年代与遗址范围的基本介绍,可见[遗址的官方说明](https://sannaimaruyama.pref.aomori.jp/about/iseki/)。

停靠在三内丸山遗址前的青森市营巴士
制定行程时,把铁路与巴士的换乘、两处设施之间的步行一起考虑。 不要只记住从市中心到其中一处的车程,就假设当天的所有移动都同样方便。

三内丸山遗址的接待大厅
室内展览适合先认识出土器物与聚落生活,再去户外辨认位置。 看复原建筑时,也可以留意说明牌上的依据:哪些来自柱穴、地层与遗物,哪些属于依据研究作出的复原解释。

阳光下的三内丸山遗址入口
参加导览能帮助把分散的遗迹连接起来,但语言与开团安排应查当日公告。 想自己走的话,可以先看[官方示范路线](https://sannaimaruyama.pref.aomori.jp/information/model-course/),按天气与体力取舍。
## 给一天留出两种节奏

青森县立美术馆的室外空间
美术馆适合在作品前停留,遗址的户外部分则需要更多步行。 可以按展览预约与天气决定先后,把午餐和休息安排在两者之间。 若只剩半天,与其匆匆把两个地方都走一遍,不如选一个认真看,把另一个留给下一次。
### 漫步青森港与睡魔之家Wa Rasse
URL: https://www.sakashita.page/aomori-port-and-nebuta-museum/
Last updated: 2026-10-24T10:00:00.000Z
青森站前的港湾区,很适合用步行认识。 睡魔之家、A-FACTORY、八甲田丸与ASPAM各有不同的主题,沿海边连接起来,便能从祭典走到铁路联络船,再回到地方工艺与食物。 这次我们按体力慢慢走,没有把每一处都安排成深度参观。

青森车站
当天的顺序是:
1. 🚶 青森站出发,先去睡魔之家Wa Rasse。
2. 在A-FACTORY稍作休息,再走向八甲田丸。
3. 沿港边看看旅客航站楼与大桥,最后到ASPAM观景、买土产。
ASPAM与阿斯帕姆是同一座建筑,不是路线里的两个独立景点。
## 睡魔之家:不在祭典当天,也能认识灯笼

睡魔之家Wa Rasse的建筑与展览入口
馆内展示大型睡魔灯笼,并通过资料介绍祭典、制作人与相关技艺。 大型作品的体积与细部都值得看,但普通展品与可触摸、可操作的体验部件要分开,按现场标志参与。

睡魔之家二楼入口附近的拍照位置

睡魔灯笼
我们一边看历史介绍,一边留意纸面、骨架与灯光怎样组成角色的表情。 同一尊灯笼从正面与侧面看,线条的层次也会改变。 想参加制作或表演体验,可查[馆方的展览与活动信息](https://www.nebuta.jp/warasse/),不沿用旧文章里的固定场次。
青森睡魔祭与弘前睡魔祭有各自的花车形式与行事安排,也不能把“自由参加”理解成没有服装、集合和会场规则。 到祭典现场之前,应另看主办方当年的参与说明。
## A-FACTORY:在海边停一下

青森港旁的A-FACTORY与青森湾大桥
A-FACTORY集合了地方物产与餐饮等内容。 我们这次只在一楼喝了咖啡,便继续往八甲田丸方向走,没有把所有餐厅与购物空间都逐一体验。 这里适合作为港边步行的休息点,是否购物可以当场决定。
## 八甲田丸:铁路怎样跨过海峡

八甲田丸附近用于铁路车辆装船的可动桥
八甲田丸曾在青森与函馆之间承担铁路联络船运输,如今以船体保存和展示相关历史。 可动桥让铁轨与随潮位变化的船舶衔接,是理解车辆怎样上船的重要设施。
日本机械学会在2011年认定的项目名称是“青函联络船及可动桥”,并非把港口所有设施笼统评为一项遗产。 具体范围与意义见[机械遗产第44号记录](https://www.jsme.or.jp/kikaiisan/heritage%5F044%5Fjp.html)。

八甲田丸船体,记录了青函联络船时代的海上交通
我们参观了船内的操纵与旅客空间,再从展望处看港湾。 从一节铁路车辆想到一艘船,再想到海峡另一边的铁路,这种交通之间的衔接,比单独记住船长或吨位更直观。

青函连络船战灾之碑
船外的战灾之碑,也提醒人们青函航路经历过战争中的人员与船舶损失。 在休闲散步途中,停下来读一段碑文,港口的历史便不只剩下繁忙运输的一面。
## 沿港边走向ASPAM

青森港旅客航站楼
因为时间有限,我们只看了旅客航站楼的外观,再往ASPAM方向走。

青森港湾大桥
沿海边路径经过大桥一带,可以看见港口、船与建筑的位置关系。 按现场开放路线步行,不需要为了抵达ASPAM而把行程理解成必须走上跨港道路。
## ASPAM:从高处看港,再挑选土产

A形状的ASPAM阿斯帕姆大楼
ASPAM的三角形外观在港边很醒目。 我们先到十三楼观景,换一个高度回看刚才走过的海岸与市区。 能看多远取决于天气,不能把某个晴天的远景当成每次登楼都能看到的内容。

阿斯帕姆13楼观景台看到的景色
观景设施与购物楼层的营业、票券安排不同,可看[ASPAM官方说明](https://www.aomori-kanko.or.jp/aspam/)。 想看影像或参加活动,也应分别确认当日运行情况。

在ASPAM看到的津轻小巾刺绣手提包

青森传统工艺津轻涂的红色器皿
最后在一楼挑了土产。 我很喜欢小巾刺绣朴素布面上的几何纹样,也留意了津轻涂器皿的漆面与花纹。 工艺名称、材质与制作方式可以慢慢比较,不必用一个统一的工序数字替所有作品作说明。

在阿斯帕姆观光物产馆--1楼购买的伴手礼
苹果点心则带回去与朋友分享。 这一天从灯笼的纸面走到船体、桥梁与漆器,不同材料串起了青森港的几种面貌。 如果还想去青森县立美术馆与三内丸山遗址,最好另外留出时间,沿着另一条主题继续走。
### 八甲田山旅游指南
URL: https://www.sakashita.page/hakkoda-mountains/
Last updated: 2026-10-24T09:49:59.000Z
八甲田位于青森县,是由多座山峰组成的火山群。 它与青森市区、奥入濑溪流、十和田湖都有关联,却不是同一个景区。 安排路线时,先分清山上散步、登山和湖畔游览,才能合理估计交通与停留时间。
## 乘缆车看山景
八甲田缆车是认识山景的一种方式。 根据[运营方的时刻与票价说明](https://hakkoda-ropeway.jp/fare%5Ftime/),单程约需10分钟;实际运转受天气影响。 到山顶站后,可以按当日开放情况和自己的体力选择散步路线,不必把乘缆车与登顶主峰混为一谈。
秋季可看红叶,冬季则以积雪和树冰景观吸引游客。 山上与市区的天气和气温可能差很多,晴天从市区出发,也不代表山上一定适合步行。
## 湿原与山麓温泉
八甲田的湿原与高山植被,是山岳风景的重要部分。 睡莲沼等地点与缆车站并不都在同一条步行线上,前往前应确认道路、巴士与步道的开放状态。 酸汤温泉位于八甲田山麓;浅虫温泉则在陆奥湾岸边,前往时需要分别安排交通。
## 延伸到青森市区与湖区
三内丸山遗址位于青森市区一侧,十和田湖游览船在湖区,青森观光物产馆ASPAM也在市区港湾附近。 从山上前往这些地点,需要另行安排交通与停留时间。 如果从青森一路前往奥入濑与十和田湖,应把它理解为跨区域行程,而不是缆车下站后的随意散步。
出发前可从[八甲田缆车官方网站](https://hakkoda-ropeway.jp/)确认运行信息,再结合住宿地与交通安排决定当天的活动。
### 仙台与伊达政宗
URL: https://www.sakashita.page/sendai-and-date-masamune/
Last updated: 2026-10-24T09:39:59.000Z
在仙台,伊达政宗的名字出现在城迹、寺院、博物馆和街头的纪念像上。 他是仙台藩初代藩主,也是理解这座城下町的一条线索。 沿着城迹与陵庙走一圈,比只记住“独眼龙”的称号,更容易看见他与仙台的关系。
## 从仙台城迹认识城下町
[仙台市的城史年表](https://www.city.sendai.jp/shisekichosa/kurashi/manabu/kyoiku/inkai/bunkazai/bunkazai/joseki/rekishi.html)记载,政宗于1600年规划城地,1601年开始修建仙台城。 如今的本丸一带可以看石垣、伊达政宗骑马像和城市景观,也能借助遗构展示想象原来的建筑布局。
仙台城的建筑经历了拆除、火灾与战争等变迁。 1945年的仙台空袭使大手门等建筑烧毁,今天的城迹则保留着石垣与遗构,也有复原的建筑可看。
## 瑞凤殿:陵庙与特别开帐
瑞凤殿是供奉伊达政宗的灵庙,装饰华丽,位于绿树环绕的经峰。 参观时可以留意门与建筑上的色彩、雕刻,以及伊达家的纹饰。
日常参观与特定日期的“特别开帐”是两件事。 瑞凤殿并不是只有七夕或忌日才能到访;休馆日、开放时间和特别活动请查看[瑞凤殿的参观说明](https://www.zuihoden.com/info/)。
## 松岛与瑞严寺
想把伊达家的历史再往海边延伸,可以另外安排松岛。 瑞严寺的本堂由伊达政宗营建,1609年完成,内部的障壁画和雕刻是参观重点。 外观与内部装饰的差异,也很值得留意。 具体可参阅[瑞严寺的境内介绍](https://www.zuiganji.or.jp/english/)。
## 怎样安排参观?
仙台城迹与瑞凤殿都涉及坡道或台阶,行程里应留出步行和休息时间。 若还想细看松岛的海湾与寺院,不妨分成两天,避免把市内与海边都压进匆忙的一天。 东京至仙台可搭乘东北新干线,所需时间与停靠站随车次而异,应按出发日核对时刻。
### 日本鹰猎:放鹰术的历史与文化
URL: https://www.sakashita.page/japanese-falconry/
Last updated: 2026-10-24T09:29:59.000Z
放鹰术利用猛禽捕猎的习性,由鹰匠训练和引导禽鸟完成放飞、追逐与返回等动作。 在日本的公园或城迹偶尔能看到演示,鸟与鹰匠之间的配合,是现场最值得留意的部分。 它与影视作品里随意放飞一只“老鹰”的想象并不相同。
## 在鹤城看到的表演

若松鹤城的放鹰术表演
观看时,可以先观察鹰匠如何让鸟停驻,再看它离手、飞行与回到指定位置的过程。 不同猛禽的体型、飞行方式和用途不同,不能把一种鸟的翼展或寿命套到所有表演用鸟身上。 也不应仅凭一场演示,推断野外狩猎的完整过程。
## 与武家文化的联系
放鹰术与日本武家社会有长期联系,德川家康爱好鹰猎的故事也常见于历史介绍。 理解这项技艺,可以从鹰匠的训练、狩猎活动与权力阶层的生活三方面入手。
联合国教科文组织于2010年将“鹰猎:活态的人类遗产”列入非物质文化遗产代表作名录,这是由多个国家参与申报的项目。 申报与参与国家可查阅[联合国教科文组织的原始决定](https://ich.unesco.org/en/decisions/5.COM/6.45)。
## 哪里能看演示?

公园放鹰表演中托着猛禽的鹰匠
东京的滨离宫恩赐庭园等场所曾举办新年放鹰术演示,但不是每天都有固定表演。 专程前往前,应查看[滨离宫恩赐庭园的活动信息](https://www.tokyo-park.or.jp/park/hama-rikyu/index.html)。 现场请遵守观览范围,不触摸禽鸟,也不要擅自喂食或用闪光灯干扰。
### 花卷温泉与宫泽贤治的童话之乡
URL: https://www.sakashita.page/hanamaki-onsen-and-miyazawa-kenji/
Last updated: 2026-10-24T09:19:59.000Z
这次去花卷,同行朋友带着孩子,我们便把温泉住宿与宫泽贤治的童话世界放在一起安排。 行程从饭店周边的瀑布开始,再去纪念馆、山猫轩与童话村。 大人可以看文学与地方文化,孩子也有适合慢慢探索的空间。
## 从新花卷站前往温泉
乘东北新干线抵达的是新花卷站,与东北本线的花卷站不同。 预订接驳或查公交时,要先确认站名。
这次入住千秋阁,利用饭店的接驳车前往温泉区。 再次安排时,应查看当前的接驳时刻与是否需要预约;火车抵达时间、行李和接驳之间,也要留一点余量。
花卷温泉属于更广的花卷温泉乡。 当地以“花卷12汤”介绍多个温泉地,挑住宿时可以先看地点、交通和浴场,而不必把整个温泉乡当成一座相连的饭店。
## 千秋阁与饭店里的小发现

花卷温泉千秋阁的入口与建筑
这次的住宿让我们有时间慢慢泡汤,也在饭店里看到了不少地方文化的线索。 千秋阁与邻近的花卷饭店、红叶馆有连通空间,住宿客人可按现行安排利用三馆的大浴场。

饭店内的历史照片与纪念展示
浴场的营业、清扫和利用条件各有规定。 带孩子同行时,先确认适合的使用方式和休息地点,按当天的体力决定泡多久。

饭店内展示的租借浴衣

饭店内的租借自行车
馆内有浴衣和自行车租借的展示,费用与服务内容应看当期说明。 我们这次带着孩子,后来选择请饭店协助叫出租车前往贤治相关设施。

以木纹、漆色和金具装饰的岩谷堂箪笥
饭店里展示的岩谷堂箪笥也很吸引目光。 可以近看抽屉上的金具、木纹与表面装饰,旅行中认识工艺,有时就从这样一件家具开始。
[千秋阁:住宿、浴场与交通www.hanamakionsen.co.jp](https://www.hanamakionsen.co.jp/senshu/)
## 稻荷神社与釜渊瀑布

花卷温泉稻荷神社的鸟居
饭店周边的稻荷神社,是散步时可以停留的一处。 参拜者寄托生意兴隆、家内平安等愿望,神社也与温泉区的历史相连。

林间通往釜渊瀑布的路标

釜渊瀑布流过宽阔岩面的水流
沿林间步道走向釜渊瀑布,可以看到河水顺着岩面铺开。 这段散步适合放在饭店住宿的前后,雨天或步道湿滑时,则按现场状况调整。 走到瀑布后,也记得给回饭店和接续交通留下时间。
## 宫泽贤治纪念馆

纪念馆周边设施与道路的导览图
宫泽贤治出生于花卷,是诗人、童话作家,也长期关心农业、自然与人们的生活。 《银河铁道之夜》等作品中的星空、矿物、动物与田野,都能与他观察世界的方式联系起来。

通往宫泽贤治纪念馆的林荫步道
纪念馆通过手稿、爱用品、影像与资料介绍贤治。 与其急着看完每一件展品,可以先从熟悉的一部作品出发,再看它与音乐、科学或农业活动之间的关系。
纪念馆与童话村位于不同地点,步行途中也有高差。 同行有孩子或不适合爬坡的读者,可以先看地图,再选择步行或乘车。
[花卷市:宫泽贤治纪念馆www.city.hanamaki.iwate.jp](https://www.city.hanamaki.iwate.jp/miyazawakenji/kinenkan/)
## 在山猫轩吃午餐

以贤治作品为主题的山猫轩餐厅
山猫轩的店名让人想到贤治的《要求特别多的餐厅》。 这次把午餐安排在这里,也让文学参观之间多了一段休息。 想按类似路线走的话,可以先查当日营业与菜单,再决定入馆和用餐的顺序。
## 宫泽贤治童话村

童话村的草坪与造型各异的展示建筑
童话村将作品中的意象变成可以走进的空间。 建筑、草地与小径之间的变化,适合让孩子按兴趣探索,大人也可以从颜色、光线和尺度里感受另一种阅读方式。

通往贤治山野草园的木栈道

“贤治的教室”里以星空为主题的展示
“贤治的教室”围绕动物、植物、星空等主题展开。 可触摸或操作的展项各有说明,跟着现场引导,比较容易找到适合孩子参与的部分。

童话村里带有列车造型的小火车
小火车又把人带回《银河铁道之夜》的想象。 如果想参加乘车或其他活动,应先确认当天是否举行,再安排停留时间。
## 一天怎样排得从容
- 🚌 抵达:新花卷站接续饭店接驳,先确认班次与预约方式。
- 🚶 温泉区:在泡汤之间安排釜渊瀑布散步。
- 🚕 贤治相关设施:纪念馆、午餐与童话村按开放日串联,最后留出回站时间。
从2026年4月起,相关市立设施的开放与休馆安排已有调整。 纪念馆和童话村通常都在9:00至16:30开放,但固定休馆日分别是星期二与星期一,遇节假日及临时开放会另作安排。 想在同一天参观两处,出发前应分别查好日历。
[花卷市:宫泽贤治相关设施与活动www.city.hanamaki.iwate.jp](https://www.city.hanamaki.iwate.jp/miyazawakenji/index.html)
### 紫阳花季的镰仓之旅
URL: https://www.sakashita.page/kamakura-hydrangeas/
Last updated: 2026-10-24T09:09:59.000Z
这次同行的伙伴大多是第一次来镰仓。 大家想拍电车与绣球花,也想了解一点城市的历史,但不太愿意花很久排队。 于是,我们放下明月院和长谷寺,选择了御灵神社、鹤冈八幡宫与建长寺。
这样的路线把海边的江之电、热闹的小町通,以及北侧山间的禅寺放在同一天。 景点之间需要乘车和步行,尤其后半段有坡道,想走得轻松一些,也可以在建长寺庭园结束行程。

镰仓站的站房与入口
## 御灵神社与江之电
从江之电长谷站步行,可以前往供奉镰仓景正的御灵神社。 神社入口靠近铁路平交道,绣球花与驶过的电车,让这一带在花季尤其热闹。

御灵神社入口与邻近铁路的街景

绣球花旁驶过的江之电
神社境内禁止摄影。 铁路周边也应遵守现场标示,不站进轨道、不在平交道内停留,更不要为等一班电车挡住参拜者或居民的通路。
[御灵神社:参观说明visit.trip-kamakura.com](https://visit.trip-kamakura.com/zh-hant/things-to-do/goryojinja-shrine%5Ftw/)
### 在力饼家停一会儿

力饼家的传统店面

力饼家的门面与点心展示
附近的力饼家是经营和式点心的老店。 想尝权五郎力饼的话,可以到店看看当天的品种、包装与保存说明。 点心适合安排在散步的间隙,也给回程留下一个具体的味道。
## 回到镰仓站,沿小町通走向八幡宫

镰仓站前的公交车与站房
从长谷乘江之电回到镰仓,再由东口走向小町通。 这段街道集中了食品与伴手礼店,适合一边走、一边挑感兴趣的小店。

小町通入口的红色鸟居
花季人多时,逛店与赶路的节奏最好稍微分开。 买到想吃的东西,可以找店家允许的地方停下来,再继续前往八幡宫。
## 鹤冈八幡宫:认识镰仓历史的一站
鹤冈八幡宫的历史,与源氏及镰仓武家政权密切相关。 1063年,源赖义在由比乡迎请八幡神;1180年,源赖朝将神社迁至今日所在的区域。

鹤冈八幡宫的朱红色楼门
沿参道与石阶向前看,建筑的层次会逐渐展开。 匾额中“八”字的鸽形设计,也可以作为观察细节的起点。
如果想把这里与春天的景色一起看,可以再读[樱花季的镰仓](https://www.sakashita.page/kamakura-cherry-blossoms/)。 境内各设施的开放时间与参观安排,则以神社当前公告为准。
[鹤冈八幡宫官方网站www.hachimangu.or.jp](https://www.hachimangu.or.jp/)
## 建长寺:从大门走进庭园
从八幡宫向北,可以步行前往建长寺。 这一段有坡度,梅雨季也可能湿滑;若选择公交,应另查当天班次,而不必为了追求最短时间赶路。

建长寺的山门
建长寺由镰仓幕府执权北条时赖于1253年创建,以南宋僧人兰溪道隆为开山。 它位列镰仓五山之首,也是临济宗建长寺派的大本山。
走进寺内,可以把山门、佛殿与后方庭园连起来看。 南宋禅僧与镰仓武士的联系,也让这段参观多了一条跨越海洋的历史线索。

建长寺庭园的池水、石景与树木

庭园小径旁的蓝紫色绣球花
这里的绣球花没有占满整个视野,而是在绿意与建筑之间出现。 慢一点走,少量花株也能成为庭园里鲜明的颜色。
[建长寺:历史与参拜说明www.kenchoji.com](https://www.kenchoji.com/)
## 半僧坊与后山的景色

通往半僧坊途中的竹林
若体力和天气允许,可以继续往后山的半僧坊走。 石阶与坡道较多,最好穿适合步行的鞋,并为下山留出时间。

半僧坊参道两侧的天狗像

半僧坊附近的天狗装饰
天狗像让后山与前面的禅寺庭园有了不同气氛。 这些形象属于当地信仰与民俗,沿参道看它们的姿态,也是上山的一部分乐趣。

后山展望处的栏杆与绿意
天气与能见度合适时,后山展望处可以眺望镰仓与相模湾方向。 若下雨或步道状况不佳,在寺内结束参观也很完整。

建长寺附近提供建长汁的店铺
寺外有提供建长汁的店铺,走累了可以看看当天菜单,安排一份热食。 这趟行程最后留下的,是电车、绣球花与山间石阶各自不同的节奏。
### 了解日本的地理与地方划分
URL: https://www.sakashita.page/geography-of-japan/
Last updated: 2026-10-24T08:59:59.000Z
准备日本旅行时,“关东”“东北”“北陆”这些地方名称,比一长串县名更容易先记住。 不过,地方名称与行政区划不是同一回事;不同交通公司、统计资料和旅游指南,也可能采用不同的区域组合。 先认清日本列岛的大致位置,再把目的地放回地图上,规划路线会轻松一些。
本文的地图素材来自[日本地図無料イラスト素材集](https://japan-map.com/)。 这些简化示意图用于说明位置与区域,不能替代精确地图。
## 日本在哪里
日本是位于东亚、欧亚大陆东侧的岛国。 列岛大体沿东北至西南方向延伸,东面濒临太平洋,西侧隔着日本海、东海与亚洲大陆相望,北海道东北侧则是鄂霍次克海。 北海道、本州、四国、九州和冲绳诸岛等地区,共同构成了旅行中常说的“日本列岛”。
日本的国土面积约为37.8万平方公里。 [国土地理院的面积调查](https://web1.gsi.go.jp/KOKUJYOHO/MENCHO-title.htm)列出的是带有调查日期的数据,其中2026年7月1日的总面积为377,973.68平方公里。 面积会随海岸变化、填海和测量更新而调整,引用精确数字时应连同日期一起看。
岛屿数量也取决于统计方法。 国土地理院在2023年公布,按一定条件利用数字地图计数的结果为14,125座岛屿;[说明资料](https://www.gsi.go.jp/common/000248894.pdf)交代了所采用的地图与周长条件。 这个数字的更新包含了测量与计数方法的变化,不能理解成短时间内新出现了同样数量的岛。
## 都、道、府、县与“地方”

日本47个都道府县的划分
日本有47个都道府县:东京都、北海道、大阪府、京都府,以及43个县。 都道府县之下,市、町、村和东京的特别区,也是查地址、找车站或阅读地方资料时经常遇到的单位。 “市”“町”“村”不能简单理解成彼此逐级隶属的三个层级。
下面按常见地方名称介绍区域。 其中,中部采用九县的范围,三重县放在近畿;冲绳单列介绍。 实际购买交通票券或查询天气时,要再看该资料对区域的具体定义。
## 北海道:城市之间要留足距离

北海道地方的位置
北海道既是岛名,也是一个行政区划的名称,道厅所在地为札幌市。 札幌、小樽、函馆、旭川和道东各地,在地图上都属于北海道,但相互之间可能有较长的移动距离。 安排旅行时,先确定主要区域,再决定是否跨区,比把所有城市都放进同一个短行程更合适。
冬季的雪景与运动,也是北海道的重要特色。 1972年在札幌举行的冬奥会,是日本及亚洲首次举办的冬季奥运会;[札幌奥林匹克博物馆](https://sapporo-olympicmuseum.jp/history/)介绍了这段历史。 北海道也是重要的农业地区,田野、牧场与城市之间的景色变化,是认识这座岛的另一条线索。 气温、降雨与积雪因地区和季节而异。
## 东北:同一地方,也有不同的海岸与山地

东北六县的位置
东北地方位于本州北部,包括青森、岩手、宫城、秋田、山形和福岛六县。 东侧面向太平洋,西侧面向日本海,内陆则有山地与盆地。 温泉、祭典、历史街区和农产风景,常常分布在不同方向,移动路线也会随之改变。
青森的苹果、山形的樱桃与洋梨、福岛的桃子,也让各地的季节有了具体的味道。 津轻漆器、会津漆器与南部铁器,则是理解东北地方手工艺时经常遇到的名称。
## 中部:九县之间,海与山的差异很大

本文采用的中部九县范围
这里所说的中部,包括新潟、富山、石川、福井、山梨、长野、岐阜、静冈和爱知九县。 这一大片区域同时包含日本海沿岸、内陆山地与太平洋一侧。 查路线时还会遇到甲信越、信越等名称,所属范围应以资料本身的说明为准。
日本海一侧可从新潟、富山、金泽和福井等城市展开旅行;内陆的长野、山梨与岐阜,则常与山岳、盆地和高原联系在一起。 静冈、爱知面向太平洋,名古屋是这一侧的重要交通节点。 山地会明显影响城市之间的走法,地图上的直线距离不等于实际交通时间。
## 关东:东京之外,还有六个县

关东一都六县的位置
关东通常指东京都与茨城、栃木、群马、埼玉、千叶、神奈川六县。 东京的城市观光之外,横滨与镰仓、日光、群马的温泉地区,以及各地的海岸和山地,也属于关东旅行的不同方向。
关东平原是这一带的重要地形,城市与交通网络向周边延伸;往群马、栃木等地走,又会遇到山地景观。 东京都的范围还包括伊豆诸岛和小笠原诸岛等离岛。 这些岛屿与市区之间的交通条件差别很大,安排时应作为独立行程考虑。
## 近畿:把城市群与半岛分开看

本文采用的近畿地区范围
本文把大阪府、京都府与滋贺、兵库、奈良、和歌山、三重五县放在近畿地方介绍。 “关西”也是旅行中常见的名称,但不同资料采用的范围不一定完全相同。
大阪、京都、神户之间有密集的铁路联系,而纪伊半岛、兵库北部或滋贺的不同湖岸,则需要另外核对车次与距离。 京都、奈良的古都景观,大阪的城市街区,以及神户的港口,让近畿成为观察日本不同历史层次的地方。
## 中国地方:山阴与山阳两侧的路线

本州西部的中国地方
日本的“中国地方”位于本州西部,包括鸟取、岛根、冈山、广岛和山口五县。 面向日本海的一侧常称为山阴,面向濑户内海的一侧常称为山阳,中国山地横贯其间。
广岛与冈山一带的城市、岛屿和濑户内海风景,与鸟取、岛根的海岸和城镇,各有不同的交通走向。 冈山与仓敷的街区、广岛与宫岛、岛根的出云大社,都是认识这一地方的不同入口。
## 四国:四个县与不同的海岸

四国四县的位置
四国包括德岛、香川、爱媛和高知四县,主要城市分别为德岛、高松、松山和高知。 北侧面向濑户内海,南侧面向太平洋,中部有山地相隔。 濑户内海沿岸的城市移动,与深入山谷或前往太平洋海岸的路线,需要不同的时间安排。
鸣门的漩涡、吉野川一带的峡谷、松山的温泉,以及高知的河川与海岸,都能成为一段旅行的中心。 濑户内海沿岸与高知一侧的气候、河流和海岸景观,也让四国呈现出不同的面貌。
## 九州:温泉与火山,也要看地区差异

九州七县的位置
九州本岛是日本主要岛屿之一;本文所说的九州地方,包括福冈、佐贺、长崎、熊本、大分、宫崎和鹿儿岛七县。 这里有福冈、长崎等城市,也有阿苏、樱岛等火山景观,以及别府、指宿等温泉地区。
九州整体偏南,山地与北部地区冬季仍会寒冷。 海岸线、山地和离岛之间的移动方式也不相同,先确定一片区域,再选择铁路、巴士、船或自驾。
## 冲绳:先选岛,再排岛上的路线

冲绳诸岛的位置示意图
冲绳县由多个岛屿组成,县厅所在地为那霸市。 冲绳本岛、宫古岛与石垣岛等目的地,各有自己的机场、港口和岛内交通条件。 那霸机场是前往冲绳本岛的主要航空门户,宫古机场则服务宫古地区。
选择“去冲绳”之前,先确认自己想去的是哪座岛。 岛际移动往往要依靠飞机或船,天气与班次会影响安排;把换岛日与岛内游览日分开考虑,行程会更清楚。
### 日本的自然地理:山川、湖泊与气候
URL: https://www.sakashita.page/japans-natural-geography/
Last updated: 2026-10-24T08:49:59.000Z
日本列岛南北狭长,山地、平原与海岸彼此交错。 旅行时,地理并不只是地图上的知识:同一天从海边进入山里,气温、积雪和交通条件都可能改变。 认识几条基本线索,比记一长串容易过时的排名更有帮助。
## 山多,平原也很重要
富士山海拔3776米,是日本最高峰。 本州中部的飞驒、木曾与赤石山脉,分别常被称为北阿尔卑斯、中央阿尔卑斯与南阿尔卑斯。 山地使道路与铁路集中于有限的山谷和通道,地图上的直线距离往往不能直接换成移动时间。
东京所在的关东平原、大阪平原和浓尾平原等,则承载着密集的城市与产业。 从大城市前往山岳景区,应把接驳巴士、缆车及季节性道路开放情况一并考虑。
## 河川与湖泊怎样影响旅行?
信浓川是日本最长的河流,利根川的流域面积最大。 湖泊中,琵琶湖的面积最大;十和田湖等火山湖,则能让人从湖岸与展望台读懂不同的地形。 河川与湖泊既是风景,也与供水、农业、交通和人们的生活相连。
## 同一个季节,也有不同天气
日本海侧与太平洋侧,在冬季降雪、降水和日照方面常有明显差异;北海道、冲绳与本州高山的气候,也不能用同一个“日本天气”概括。 日本气象厅的季节统计通常以3月至5月为春、6月至8月为夏、9月至11月为秋、12月至2月为冬。 二十四节气是另一种历法与季节表达,不能把立春直接当作各地已经转暖的保证。
准备行李时,先看目的地与海拔,再看具体日期的预报。 气候的入门说明可参阅[日本气象厅的气候知识](https://www.jma.go.jp/jma/kishou/know/)。
## 地区划分只是认识地方的起点
“东京圈”“关西”“中部”等名称,会因统计口径或使用场景而有不同范围。 方言的划分也取决于采用的语言学分类与观察范围。 读旅游资料时,先确认它讨论的是自然地形、行政区,还是交通与生活圈,才不容易把不同层次的地图混在一起。
### 平泉的世界文化遗产
URL: https://www.sakashita.page/hiraizumi-world-heritage/
Last updated: 2026-10-24T08:39:59.000Z
这次与朋友从东京向北旅行,我们先把行李寄存在一关的饭店,再轻装去平泉。 同行朋友带着JR东日本的通票,乘新干线到一关、换东北本线到平泉,让行程衔接得很顺利。
我们只选了毛越寺与中尊寺两处作为重点,傍晚再去严美溪。 两座寺院各有值得慢慢看的地方,留够参观时间,比把所有遗产地都赶完更适合这一天。
## 从平泉站开始

平泉站前的公交与观光设施
当时到站后,我们很快找到巡回公交“るんるん”。 它可以把平泉站、毛越寺与中尊寺等地点连接起来,适合与步行搭配。
这条公交按季节和指定日期运行。 再次安排时,应看本年度的运转日和末班;没有巡回车的日子,也可以比较其他公交或出租车。
[岩手县交通:平泉巡回公交iwatekenkotsu.co.jp](https://iwatekenkotsu.co.jp/runrun%5F0419.html)
## 世界遗产包括哪些地点
2011年,“平泉——表现佛国净土的建筑、庭园与考古遗迹”列入世界遗产。 它由中尊寺、毛越寺、观自在王院遗址、无量光院遗址与金鸡山五处组成。
这些地点共同表现了平泉的宗教与城市景观,也包括今天以遗址形式保存的空间。 看现存建筑、庭园和考古遗迹时,可以分别理解它们保存下来的内容。
[联合国教科文组织:平泉whc.unesco.org](https://whc.unesco.org/en/list/1277/)
## 毛越寺:先看复原图,再走庭园

毛越寺的山门

入口附近展示的历史复原图
毛越寺的创建由绪与慈觉大师圆仁的传承相连,12世纪又在奥州藤原氏的支持下发展。 从入口的复原图,可以先认识当年建筑与池塘的关系,再到现场寻找留下来的布局。

林间的芭蕉句碑

毛越寺宝物馆
我们也进宝物馆看了历史资料和出土物,再继续往本堂与庭园走。 俳句碑、展览与遗迹放在一起,让这段散步多了不同年代的线索。

毛越寺本堂

净土庭园的大泉池与岸边树木
今天的本堂建于1989年,庭园与遗迹则保留着更早的结构,并经过调查与整备。 池中立石、岸边石组与引水,共同构成了净土庭园的空间。
我们在这里走了约一个半小时。 看池面的涟漪、树木与地上的苔藓,时间很自然地慢了下来。
[毛越寺:庭园、历史与参观www.motsuji.or.jp](https://www.motsuji.or.jp/)
## 中尊寺与金色堂
离开毛越寺后,恰好赶上巡回公交,便前往中尊寺。 参道有坡度,也分布着多处堂宇,最好把走路、休息与主要参观区分别留出时间。
金色堂建于1124年,是认识平泉文化的重要建筑。 它以金箔、漆艺与螺钿等装饰表现佛教净土,细节值得近看。

保护金色堂的新覆堂外观
从林间走近时,先看到覆堂的保护建筑,真正的金色堂收藏在其中。 走入内部,眼前才展开那片金色的空间。
金色堂也与奥州藤原氏的追思相连,须弥坛内保存着相关遗体。 这里是佛教礼拜与文化财保存的空间,参观应遵守寺方的摄影及其他规定。
## 赞衡藏与旧覆堂

金色堂主题的照片展示与纪念拍照装置
赞衡藏介绍寺院保存的佛像、美术与历史资料,原物的展示范围和摄影规定各有安排。 照片里的纪念展示,不代表其他展品也都允许拍摄。

旧覆堂内的木构与纪念碑
旧覆堂曾长期保护金色堂,后来随着新的保护建筑设置而移筑保存。 看它的屋顶、木构与内部空间,也能认识文化财怎样被历代人保护下来。

中尊寺参道的木造山门
我们在中尊寺停留约三小时,包含午餐、其他堂宇与买小礼品的时间。 这段停留,也让参观从一座金色建筑延伸到了整片寺院。
[中尊寺:境内与参观规则www.chusonji.or.jp](https://www.chusonji.or.jp/)
## 在礼品店看到的南部铁器

店内展示的南部铁壶
南部铁器是岩手很有代表性的工艺,铁壶、风铃与其他器物各有不同用途。 照片中的铁壶,让我们在寺院参观之外,也留意到当地材料与制作的传统。 真要购买时,可以先看重量、容量和保养方式,选择适合自己日常使用的一件。
## 傍晚去严美溪

严美溪的岩壁与水流
我们返回平泉站,再乘电车到一关,换公交去严美溪。 这段移动需要配合班次,尤其傍晚出发时,要先确认回程。

一关站前的公交站牌

严美溪碧绿的溪水与跨谷小桥
严美溪位于磐井川,岩壁与水流构成了很鲜明的河谷景观。 沿开放步道走,可以从不同位置看水色与岩石。
当地的“飞翔团子”会用绳索和篮子把点心送过河。 这次到得较晚,已经错过营业,却看到了落日时的溪谷。 没吃到团子有些遗憾,傍晚的景色仍让这一段值得记住。

树木环绕的河谷与水面
## 怎样安排更从容
- 🚃 去程:东京至一关的新干线,再换东北本线到平泉。
- 🚌 平泉:按当日公交,选择毛越寺与中尊寺的顺序。
- 🚶 严美溪:另留移动和散步时间,先核对一关往返公交。
如果一天里只想慢慢看寺院,也可以把严美溪放到次日。 平泉的庭园与参道,很适合留出不必追赶下一班车的一段时间。
### 平泉中尊寺:走访世界文化遗产
URL: https://www.sakashita.page/chusonji-temple/
Last updated: 2026-10-24T08:30:00.000Z
中尊寺位于岩手县平泉,是天台宗在东北地区的大本山,也是平泉世界文化遗产的重要组成部分。 沿着杉树环绕的参道慢慢登山,可以走进奥州藤原氏留下的寺院与佛教艺术世界。
## 中尊寺与平泉
平安时代后期,藤原清衡以平泉为中心修建寺院,希望在经历战乱的土地上表现佛国净土的理想。 金色堂建于1124年,是中尊寺保存创建时期面貌的代表建筑。
## 月见坂

表参道月见坂
乘观光巴士到中尊寺后,可以从月见坂一侧的表参道登山。 参道两侧老杉成荫,沿途的堂宇与山林交织在一起,适合放慢脚步参观。
## 赞衡藏

赞衡藏宝物馆
赞衡藏收藏约三千件与奥州藤原氏相关的文化财,包括佛像、佛经与随葬品。 参观这里,再去看金色堂,有助于理解当时工艺与信仰的关系。 室内请遵守现场的摄影规定。
## 金色堂

中尊寺金色堂的保护建筑
金色堂以金箔、莳绘与螺钿装饰闻名,细看柱子与须弥坛,可以发现贝壳与金工组成的精细纹样。 堂内供奉阿弥陀如来等佛像,也安置着奥州藤原氏历代的遗体与首级。 金色堂是保存下来的历史建筑,外面的覆堂则承担保护它免受风雨侵蚀的作用。
## 旧覆堂与本堂

中尊寺覆堂内的木柱与梁架
旧覆堂曾经包覆金色堂,后来在金色堂修理时迁移至现址。 参观时可以留意其朴素的木结构,与金色堂华丽的装饰形成不同的观感。
本堂是山内举行法要的重要场所,供奉释迦如来,并护持天台宗传承的不灭法灯。 走完主要堂宇后,沿参道下山,再接着前往平泉的其他景点。
[中尊寺境内导览与各堂介绍www.chusonji.or.jp](https://www.chusonji.or.jp/around/)
### 春日奥入濑溪流:住一晚,沿水走进新绿
URL: https://www.sakashita.page/oirase-gorge-in-early-spring/
Last updated: 2026-10-24T08:20:00.000Z
这次我们在奥入濑溪流住了一晚,第二天沿水向上游走,再乘巴士去十和田湖。 春天的树叶刚刚展开,岩石上的苔藓和水边的小花,让一路都有值得停下来的细节。
奥入濑溪流位于青森县,从十和田湖子之口至烧山一带约14公里,属于十和田八幡平国立公园。 整段走完需要较长时间,也可以按交通与体力选择其中一段。
## 先看地图,再决定走到哪里

2024年到访时参考的巴士时刻表,仅作当时行程记录

奥入濑溪流散步路线与景点导览图
溪边步道与公路距离很近,不同区段的路况和可停靠巴士站也不一样。 出发前先确定终点、返程班次与可提前结束的地点,再把拍照和休息时间放进去。
照片中的时刻表属于2024年的旅行记录,不能直接用于现在的换乘。 当前步道、交通管制及季节性通知,可查看[十和田官方旅游信息](https://www.towada.travel/news)。
## 第一晚:住进溪流旁的酒店

奥入濑溪流酒店内冈本太郎创作的巨大暖炉
酒店大堂里的巨大暖炉很有存在感。 冈本太郎的《森林神话》把鸟、菌类与森林生命的意象放在一起,窗外则是真实流动的溪水与树林。

奥入濑溪流酒店的露天温泉入口

苹果餐厅内红苹果造型的灯饰
入住后去泡汤,再到苹果主题的餐厅吃饭,是第一天简单而舒服的安排。 餐厅上方一颗颗红苹果造型的灯饰,让青森的地方特色变得很直观。
## 第二天:从烧山向上游走

酒店外初春新绿中的奥入濑溪流
第二天早上10点半,我们从酒店出发,先到附近的奥入濑溪流馆。 原先准备把铫子瀑布附近的巴士站作为步行终点,沿途再按同行者的体力调整。

奥入濑溪流馆的木造入口
溪流馆介绍当地的森林、鸟类与自然环境,也能获取散步信息。 先认识几个沿途会遇到的名字,再走进溪边,眼前的风景就更容易看出层次。

石户休息处内的展览与树木展示
石户休息处是中途可以停留的一站。 再往上游,急流、瀑布和岩石之间的距离逐渐缩短,走走停停,很容易比预计多花时间。

奥入濑溪流阿修罗之流的急流与苔石

树林与苔石间的云井瀑布

沿岩壁细细流下的白糸瀑布

不老瀑布与奥入濑溪边的苔石
阿修罗之流、云井瀑布,以及对岸细细落下的水流,各有不同的声音与形状。 不必把每一处都当作必须完成的打卡点,看到喜欢的地方,多停几分钟就好。
## 在酒店里也能看溪流

酒店庭院看到的奥入濑溪流

奥入濑溪流酒店庭院与面向树林的建筑
酒店旁就能看到水流和刚发芽的树木。 住在这里的好处,是不用等正式徒步开始,散步的感觉就已经在窗边和庭院里展开。

奥入濑溪流酒店内的温泉浴场入口
我们更喜欢面向溪流的露天浴场。 各浴场的开放与清扫时间不同,现在预订时应查看酒店当前安排。

酒店里看到的当地伴手礼包装

青森窑烤苹果派的包装
苹果点心和当地礼品也很适合慢慢挑。 吃过早餐后再出发,背包里带上饮水和一点补充食物,沿途会更从容。
[奥入濑溪流酒店:住宿、活动与交通hoshinoresorts.com](https://hoshinoresorts.com/ja/hotels/oirasekeiryu/)
## 溪流馆:先看见小小的苔藓世界

关于奥入濑溪流常见鸟类介绍

各种青苔
在溪流馆,我们用显微镜观察苔藓。 平常看起来像一片绿色的表面,放大之后竟有细小而清楚的叶片,让人很想再回到林中仔细看看。
## 下游:树影与较开阔的水面

初春奥入濑溪流下游的树林与河面
靠近烧山的一段,水面相对开阔,光线也更容易穿过树梢。 枝条才刚泛绿,树形、河岸和水色都看得很清楚。

白色水花穿过奥入濑溪流的苔石
春天的景色不只是嫩叶。 水流经过苔石时翻出的白色浪花,也把绿色衬得更鲜明。
## 石户与阿修罗之流

奥入濑石户休息处

巨石倚靠树木形成的石户景观与说明牌
石户的巨岩倚靠在树旁,形成像小小岩屋的空间。 当地还流传与女盗贼阿松有关的故事,路旁说明为这块岩石添了一点传说的色彩。

阿修罗之流
阿修罗之流的水势很有力量,溪水撞上岩石,再从不同的缝隙间冲出。 在步道上停下来听水声,比只拍一张照片更能记住这一段。
### 云井瀑布

云井瀑布
云井瀑布从林间岩壁落下,是沿途很醒目的一处。 从主步道靠近瀑布时,留意湿滑地面,也给其他游人留出通过的位置。
## 向上游:小花与瀑布
有同行朋友走累了,便在中途改乘巴士前往十和田湖。 其他人继续沿溪边走,最后从铫子瀑布附近搭车。

蕨类和成片盛开的小花
林下的蕨类和小花很可爱,近看时请留在开放步道上。 苔藓、植物和倒木都是这片环境的一部分,拍照后把它们留在原处即可。

奥入濑溪边的瀑布、苔石与初春新绿
这一段溪流两侧有不少瀑布,不同角度能看到水线、岩壁与树木怎样交叠。 我们最后乘巴士继续前往十和田湖,结束了这段沿水向上的散步。
## 出发前确认什么
- 🎒装备:饮水、简单食物、雨具、保暖外套,以及适合湿滑步道的鞋。
- 🚌交通:当天的去回班次、末班时间和步行终点,另准备可缩短路线的停靠点。
- 步道:积雪、落石、施工与野生动物等都可能影响开放,请按最新公告和现场指示调整。
春天可能还有残雪,树荫里的体感温度也会偏低。 如果有熊出没警告或临时封闭,应改变路线;熊铃不能代替对当前开放情况的确认。
想接着游湖,可以参考[十和田湖周边的行程](https://www.sakashita.page/lake-towada-area/),但要先把两段之间的交通时间留够。
### 十和田湖周边观光
URL: https://www.sakashita.page/lake-towada-area/
Last updated: 2026-10-24T08:09:59.000Z
十和田湖位于青森县与秋田县交界,是火山活动形成的破火山口湖。 湖岸、半岛与周围的山林,构成了层次丰富的景观。 如果把它与奥入濑溪流安排在一起,应把道路和巴士移动也算进行程,不能只凭地图上相邻就低估距离。
## 从休屋湖畔开始
初次到访,可以把休屋一带作为认识湖区的起点。 沿湖散步,前往乙女之像、十和田神社,再找一处地方坐下来,看湖面的光线和天气变化。 高村光太郎创作的乙女之像位于这一带,不是在湖东岸任意一个观景点。
如果自驾,还可以按道路开放情况前往较高处的展望点。 湖边与高处看到的景色很不一样,但遇到雾天时,也不必勉强追逐全景。
## 乘船看湖,与环湖不是一回事
游览船能让人从水上看中山半岛、御仓半岛等地形。 航线、出发码头与航行时间依运营安排而定,并不是所有班次都会“环湖一周”。 冬季的运行也不能直接套用春至秋的安排,购票前应确认运营日期与当天班次。
## 交通与季节
从青森、新青森、八户等方向前往湖区,需要结合季节性巴士与道路情况规划。 十和田观光电铁已经停运,前往湖区应重新确认现行巴士路线。
湖区的住宿、餐饮与公共交通在不同季节差异较大。 先确认返程与最后一班交通,再决定散步和乘船的时间,会比到站后临时拼接路线更稳妥。 路线和当地信息可从[十和田湖国立公园协会](https://towadako.or.jp/)开始查阅。
### 松岛一日游:朱桥、古寺与海湾游船
URL: https://www.sakashita.page/matsushima-day-trip/
Last updated: 2026-10-24T07:59:59.000Z
松岛的海岸适合慢慢走。 松树覆盖的小岛、连接岸边的朱桥,以及伊达家留下的建筑,把海景与历史放在了很近的距离里。
从仙台出发,可以乘JR仙石线到松岛海岸站。 JR东北本线的松岛站是另一个车站,安排行程时先确认目的地与步行距离。
## 沿海岸认识几处重点
### 雄岛与渡月桥

松树覆盖的雄岛与朱红色渡月桥
雄岛保留着宗教修行与供养的遗迹。 它适合作为海岸散步的一端,从小桥走入树林,气氛便安静下来。
### 观澜亭

观澜亭面向松岛湾的木造长廊
观澜亭位于海岸高处,既能看历史建筑,也能坐下来喝茶、望海。 如果希望把一天的节奏放慢,可以先在这里留一段休息。
### 五大堂

松岛五大堂的正面入口
五大堂离主要海岸步道很近。 从桥面到堂宇,短短一段路就能看见朱桥、木雕与海面的组合。
### 福浦桥与福浦岛

笔直伸向福浦岛的朱红色福浦桥
福浦桥一直伸向海中的绿岛。 若想走进树林、从岛上换一个角度看海湾,就把福浦岛也放进行程。
### 瑞严寺与历史馆

瑞严寺的木造山门

伊达政宗历史馆的入口招牌
瑞严寺适合细看建筑和庭院,伊达政宗历史馆则用人物模型与场景展示讲述战国时代。 两处的内容不同,可以按自己的兴趣分配时间。
### 从海上看群岛

停泊在松岛码头的仁王丸游览船
游览船让视角从海岸转向岛屿之间。 先确定想乘的航班,再把岸上的散步填进前后时段,比较容易兼顾。
## 雄岛:岩窟与石碑

雄岛岩窟内排列的石塔与佛教遗迹
雄岛曾是修行与供养的场所,岩窟、石塔和碑文留下了这层历史。 沿路看这些痕迹,比只把它当成一个海景拍照点,更能体会这里安静的气氛。

雄岛树木与花丛间的诗碑
松岛也与松尾芭蕉的旅行相连。 在海岸和寺院之间散步,时常能遇见诗碑,以及后人为这段文学记忆留下的纪念。

通向雄岛的朱红色渡月桥
连接海岸与雄岛的渡月桥,还有“断去恶缘”的地方传说。 这类说法为小桥添了故事,桥本身通向的树林和岩岸,也值得慢慢看。
## 观澜亭:障壁画与一碗抹茶

观澜亭室内的障壁画、匾额与木构
观澜亭传为伊达政宗从丰臣秀吉处受赐的伏见城建筑,先移到江户,后由第二代藩主忠宗迁至松岛。 它曾用于伊达家成员与来访者的住宿、接待和赏景。
室内的障壁画与海湾景色各有值得细看的地方。 参观和摄影时,按现场指定的区域行动即可。

观澜亭的一碗抹茶与双色和果子
坐下喝抹茶,再吃一点和果子,海岸散步也有了一个自然的停顿。 茶点菜单会变,照片记录的是这次到访时的选择。
[观澜亭:历史与当前参观信息www.town.miyagi-matsushima.lg.jp](https://www.town.miyagi-matsushima.lg.jp/page/1140.html)
## 五大堂:近看屋檐与木雕

松岛五大堂的木造堂宇与石灯笼
五大堂供奉五大明王,现有建筑由伊达政宗于1604年造营。 屋檐下的雕刻、木构和十二生肖装饰,是近看时容易发现的细节。
连接小岛的桥能透过缝隙看到下方海面。 过桥时留意脚下,也给其他参拜者留出通行空间。
[瑞严寺对五大堂的介绍zuiganji.or.jp](https://zuiganji.or.jp/guide/keidai%5F15.php)
## 福浦桥:把散步延伸到岛上

连接松岛海岸与福浦岛的长长朱桥
朱红色福浦桥全长252米,尽头连接福浦岛。 站在桥上,两侧的小岛与海面逐渐展开,风景与岸边又有些不同。

从福浦桥眺望松岛湾的小岛
岛上有树林、步道、观景点和弁天堂。 过桥之后仍需要留时间在岛内走一圈,不能只把桥上的几分钟算作整个景点的停留。

福浦岛上的弁天堂
福浦桥按季节有不同的关闭时间,夜间亮灯也不表示可以继续过桥。 票价、开放与照明安排,可查看[松岛町的福浦桥指南](https://www.town.miyagi-matsushima.lg.jp/page/1578.html)。
## 瑞严寺:伊达家的菩提寺

瑞严寺本堂前的院落与树木
瑞严寺的创建传承与慈觉大师圆仁相连,后来成为临济宗妙心寺派的寺院。 伊达政宗主持的大规模造营于1609年完成,寺院也成为伊达家的菩提寺。

瑞严寺庭院
本堂、库里与廊下等建筑被指定为国宝。 本堂内的障壁画展示也包含精细的复原模写,原作与文化财的保存展示另有安排。

瑞严寺宝物馆青龙殿的馆名石刻
宝物馆青龙殿介绍寺院与伊达家相关的美术、工艺和历史资料。 本堂等室内有摄影限制,入内前先看寺方说明。
[瑞严寺:境内、参观与历史zuiganji.or.jp](https://zuiganji.or.jp/)
## 伊达政宗历史馆

伊达政宗历史馆内的骑马武将模型
馆内利用人物模型和场景,表现伊达政宗的生平与时代背景。 这类展示适合先建立人物关系和事件轮廓,再和仙台城址、瑞严寺等实地参观联系起来。
头盔纪念拍照等体验有各自的利用条件,出发前可看[历史馆当前的参观说明](https://date-masamune.jp/information/)。
## 松岛湾游船

航行在松岛湾的观光游船
仁王丸航线是从松岛出发、再回到松岛的巡游路线,所需时间约50分钟。 船从海面经过不同的小岛,能近看岸上不易观察的岩壁与岛屿轮廓。
买票、候船和从车站步行到码头,也需要时间。 冬季航班与天气停航另有安排,先查[船公司的时刻表](https://www.matsushima.or.jp/timesheet/),再决定岸上行程。
## 海岸边吃什么

旅行途中品尝的笹鱼糕
竹叶形鱼糕是这一带熟悉的味道。 鱼肉糜成形后烤制,拿在手里吃也很方便。

松岛海岸餐饮店前的菜单告示

松岛牡蛎咖喱面包的外带窗口
海岸附近还有牡蛎料理、牡蛎咖喱面包等选择。 想坐下来吃一顿,就提前找好店;只想简单休息,也可以按当天看到的店铺和排队情况决定。
## 一天保留多少个重点
- 🚆从仙台到松岛海岸站,步行进入海岸景区。
- 🚶上午:雄岛与观澜亭,或改为先参观瑞严寺。
- 🚢按已选航班乘船,午餐后再去五大堂和福浦岛。
如果想在每座寺院和岛上都慢慢走,一天会比较紧。 选一段海岸、一处重点寺院,再加一次游船,就已经能看到松岛很丰富的表情。
### 会津若松一日游:博物馆、七日町与乡土料理
URL: https://www.sakashita.page/aizu-wakamatsu-day-trip/
Last updated: 2026-10-24T07:49:59.000Z
会津若松的一天,可以从鹤城开始,再把博物馆、七日町的老建筑和一顿乡土料理接在一起。 这次旅行让我记住的,不只是城楼,也有化石的触感、旧商家的门面,以及午餐里细碎而丰富的食材。
## 当天的路线与沿途风景
- 🚶 鹤城城址公园出发,步行前往福岛县立博物馆。
- 🚌 再利用市内周游巴士前往七日町,午餐后串起野口英世青春馆与福西本店周边的街区。
鹤城的参观另记在[会津若松鹤城游记](https://www.sakashita.page/tsurugajo-castle/)里。 下面先用沿途的照片,把这一天的几个地点连起来。

蓝天下的会津若松鹤城天守阁

福岛县立博物馆的入口

会津七日町站的站房与时钟塔

渋川问屋门前的蓝色暖帘

野口英世青春馆与会津一番馆的门面

福西本店的传统建筑
这些地点并非都紧挨着车站。 排路线时,把较远的一段交给巴士,再在七日町和野口英世青春通一带慢慢走,会比每到一处就重新找交通更从容。
## 福岛县立博物馆:让历史变得可以接近
从鹤城步行到福岛县立博物馆,是很自然的衔接。 馆内的综合展示把福岛的自然、考古、历史与民俗放到较长的时间线上,看完城郭再进博物馆,也能把刚才看到的建筑放回地方历史里理解。

馆内的雪国生活与制作展示空间

博物馆内的化石标本

那次参观时可以触摸的化石展示
那次让我印象深刻的是可以上手触摸的化石展示。 平常只能隔着展柜观看的东西,一旦能摸到纹理与重量,距离感也跟着变了。 能否触摸仍以现场标示和工作人员说明为准,不能把一个体验展区的规则套用到其他展品。
企划展会更换展期,开馆日与参观安排可先查[福岛县立博物馆](https://general-museum.fcs.ed.jp/)。
## 七日町与渋川问屋:在老街吃午饭

七日町站的站房
七日町适合把速度放慢。 车站、店铺与老建筑的尺度都比较亲切,可以顺着街道走,看到喜欢的门面再停下来。 我们这天把午餐安排在渋川问屋,一家由旧海产批发商家建筑延续而来的餐馆与住宿设施。

保留旧商家氛围的渋川问屋
室内的陈设与空间,让吃饭也带上了一点看老建筑的乐趣。 那天的套餐里有干海产做成的菜肴,也有会津乡土料理“こづゆ”。 这是一道以干贝等食材熬出汤味,再配入蔬菜、木耳与麸等材料的汤菜;具体用料会随家庭与店家而不同。

到访时点的乡土料理套餐,2400日元,清酒另计
照片中的价格是那次用餐的记录,不能当作现在的菜单报价。 如果希望在老建筑里吃一顿完整的乡土料理,订位时也可以一并确认用餐时段和套餐内容。
## 野口英世青春馆:街道里的人物线索
野口英世青春馆与会津一番馆所在的建筑,让野口英世年轻时期的经历与这片街区有了具体的连接。 一楼的咖啡馆和楼上的资料展示,也使这里同时具有休息与参观的意味。

野口英世青春馆与会津一番馆
不少人认识野口英世,是因为他的肖像曾被用于2004年开始发行的千元纸币。 2024年开始发行的新千元券则采用北里柴三郎的肖像,属于不同的版式。 版式与发行时间可核对[日本银行的纸币资料](https://www.boj.or.jp/note%5Ftfjgs/note/valid/validnotes.pdf)。
## 福西本店:看门面,也看器物
福西本店离这一带的散步路线不远,适合接在咖啡休息之后。 旧商家的建筑与店内陈列,可以放在一起看:外面是街道的历史感,里面则是漆器、玻璃、蜡烛等与会津生活相连的器物。

福西本店的店面
不急着买东西时,也可以观察器物的材质、颜色与做工。 比起把当地工艺只当成“伴手礼”,看看它们怎样进入日常生活,散步就多了一层内容。

这次旅行买下的会津小物
## 周游巴士怎么用

会津若松市内周游巴士“ハイカラさん”

市内周游巴士“あかべぇ”
两条周游路线的行驶方向不同,上车前先确认目的地与先后顺序,可以避免为了去附近一站却绕行很久。 如果一天内会多次搭乘,再比较单次车费与一日券是否合适。 路线、现行票价、支付方式和临时绕行,应以[会津巴士的周游巴士页面](https://www.aizubus.com/rosen/machinaka-shuyu)为准。
这一天的重点,是把城、博物馆与老街连起来读。 不用为每个地点都留下相同的时间,也不必把路线走成一张必须完成的清单。
### 大内宿:走进旧时的驿站街道
URL: https://www.sakashita.page/ouchi-juku/
Last updated: 2026-10-24T07:39:59.000Z
大内宿位于福岛县下乡町,曾是连接会津与日光今市方向道路上的驿站。 茅草屋顶沿街排列,店铺与住居至今仍相互重叠。 它不是一座与外界隔绝、停留在江户时代的布景村,眼前的景观也来自居民持续的修缮与生活。
## 先把最后一段交通查清
乘铁路时,通常以会津铁道的汤野上温泉站为转乘点,再安排巴士或出租车。 季节巴士与冬季道路条件各有变化,去程之外也要先看返程,不能只记一个固定的出租车分钟数。
自驾则按实际出发地查高速出口和道路,不把白河、会津若松等不同方向混成同一条路线。 [观光协会的交通信息](https://ouchi-juku.com/detail/622/freepage-4.html)是确认接驳与季节安排的入口。
## 驿站街道与今天的生活
大内宿在江户时代作为驿站发展,1981年被选定为重要传统建造物群保存地区。 这里不只是屋顶样式值得看,街道、宅地、水渠与沿路房屋的关系,也保留了认识旧时往来的线索。
[观光协会的历史介绍](https://ouchi-juku.com/detail/622/freepage-1.html)可以与现场资料一起读。 不少建筑仍作店铺与住宅使用,各家开放时间不同,看到有人生活的部分时,保留一点距离,不把门前与水渠当成随意进入的拍摄空间。
## 茅草屋顶为什么能留下来
屋顶需要定期更换材料,也需要防火和共同维护。 今天的完整街景,并不意味着每根茅草都从江户时代原封不动地保存至今。 居民与工匠不断投入的劳动,才使这段景观得以延续。
走到街道一端的高处看屋顶时,也先确认台阶与开放情况。 雨雪天路面状态不同,能在低处慢慢看,就不必为了同一张俯瞰照片勉强上行。
## 吃一顿,再走一段
当地常见以大葱搭配的荞麦面,也有烤物与其他店家各自经营的餐点。 选一家感兴趣的店坐下来,比把所有名字都列成“必吃”更轻松。 营业与售卖内容随季节、各家安排而异,不保证每次都能买到旧文章里的同一份菜单。
冬季活动让茅草屋与雪景有了另一种表情,但日期与交通需另查当年通知。 想看活动,与普通日子的街道散步,是两种不同的行程准备。
## 把目光稍微放远:会津工艺

会津漆器的红色漆盒与杯具
会津漆器与乡土玩具,是理解这片地区的另一条线索。 照片中的漆盒与杯具作为相关工艺配图,不代表它们一定陈列在大内宿某一家店内。 挑选土产时,看看材质、制作者和使用方式,会比只把物品当成“日本风”装饰更有意思。
如果还要继续去会津若松,可以把城市里的工艺与博物馆安排在另一天。 在大内宿,可以好好吃一顿饭,再沿街看看茅草屋与屋檐。
### 弘前樱花节游记
URL: https://www.sakashita.page/hirosaki-cherry-blossom-festival/
Last updated: 2026-10-24T07:29:59.000Z
弘前的樱花不只有树上的一种看法。 抬头是枝条,低头是水面,走到桥边又能把护城河、城郭与远处的岩木山放进同一个视野。 这篇保留的是2024年春天的赏花记录,活动日期与照片都属于那一季。
## 城郭是风景的一部分

弘前城现存天守,摄于原旅行记录
弘前城于1611年建成,最初的天守后来因雷火烧毁。 眼前的现存天守建于1810年,延续至今已有两百多年。 城门、橹、桥与护城河一起构成了公园的层次,也让赏花路线不只是一条树木清单。
## 沿着小径看不同的樱花

弘前公园内盛开着的垂枝樱

弘前公园内远望岩木山
染井吉野、垂枝樱与较晚开放的品种,让园内不同位置的花况有先后。 城郭前、林间小径与水边的光线也不一样,同一天走过去,看到的不会只是同一张粉色背景。
公园的樱树经过长期栽培与维护,花景来自持续的照料,不能简单归结为“日本最好的技术”这样的排名。 看古树时,给枝条与根部留出空间,也是在保护以后还能看到的风景。
## 春阳桥与西护城河

从春阳桥远观西护城河两岸的樱花
走到桥边,樱花与水面的距离变得更近。 岸上的枝条朝水面展开,人沿着小径前进,视角也不断改变。 这段路适合慢慢走,而不是只在桥头挤着拍完一张就离开。
## 花瓣落到水面以后

弘前公园东门护城河面的“花筏”
花瓣聚在护城河水面上,便形成常被称为“花筏”的景象。 它不是与满开完全同步、每天都有的固定节目。 风、雨与水流都会影响花瓣停留的位置,因此不必只用树上的花量判断这一天是否值得来。
## 2024年的会期与会场

露天小吃店鳞次栉比
原先公布的2024年会期为4月19日至5月5日,后来配合开花情况提前至4月12日开始相关安排。 观光协会保留的[2024年活动资料](https://www.hirosaki-kanko.or.jp/individual/machinaka%5Fevent/event%5Fsakura202404.pdf)还区分了摊位、娱乐设施与其他项目的营业日期。 “祭典开始”不意味着所有区域同时按同一时间表运作。
四之丸等区域的摊位,为赏花添了热闹的一面。 看花、吃东西和参加游戏各花多少时间,可以当场决定,不必把整个公园压成一份精确到分钟的打卡表。
## 夜樱与下次再来的准备

夜里华灯下的樱花
入夜以后,灯光与水面倒影让同一段护城河有了另一种颜色。 夜间点灯的日期与结束时间,要查当年的公告,不能沿用2024年的安排。
弘前城同时在进行长期修理。 天守自2025年11月24日起暂停内部公开,公园散步与登楼参观需要分开理解,最新情况可查[弘前市的修理项目](https://www.city.hirosaki.aomori.jp/ishigaki/index.html)。 下次再去,先看[观光协会的开花实况](https://www.hirosaki-kanko.or.jp/edit.html?id=cat02%5Fspring%5Fsakura%5Fkaika),再决定想看满开、花筏还是晚樱。
赏花时不攀折花枝、不践踏根部,垃圾按现场规定处理。 给拍照的人留一点空间,也给仍在缓慢行走的人留一条路,这样的春日散步会舒服得多。
### 会津若松鹤城之行
URL: https://www.sakashita.page/tsurugajo-castle/
Last updated: 2026-10-24T07:19:59.000Z
这次在会津若松,我们把鹤城、茶室与绘付体验安排在同一天。 城内可以看历史与石垣,也能坐下来喝一碗抹茶,行程的节奏很丰富。
## 先认识城址的路线

鹤城会馆的入口
从公交停靠点走向城址,鹤城会馆可以作为找路和休息的一站。 接着进入北出丸,沿坡道向天守一带走。

城内管理事务所与导览处

鹤城稻荷神社
我们经过导览处与稻荷神社,再到天守参观。 稻荷信仰里的狐狸通常被视为神的使者,看到相关雕像时,可以连同神社的由绪一起理解。

鹤城天守的外观

连接城内防守空间的铁门

茶室麟阁的木门与入口

鹤城会馆的绘付体验空间
天守、铁门与茶室之间,都能安排步行。 想参加绘付体验,可以先确认接待与结束时间,再决定先看建筑还是先做手作。
## 从黑川到若松城

与城址传说相连的稻荷神社
鹤城也称若松城。 当地把早期城郭的起源追溯至芦名氏,后来又经历伊达、蒲生、上杉、加藤与保科、松平等家族的更替。
蒲生氏乡在16世纪末整备城郭与城下町,若松的名称也与这一时期相连。 从早期七层天守到后来的五层形制,城的样子随着战争、地震与改建持续变化。
1643年入主会津的保科正之,是第二代将军德川秀忠之子,也曾成为保科家的养子。 家族关系与藩主世代不同,认识这段历史时,可以把人物家谱与入主会津的年代分别看。
1868年戊辰战争中,城遭到围攻。 1874年旧天守被拆除,今天的天守于1965年重建,内部作为博物馆利用。
[鹤城:历史与当前参观信息www.tsurugajo.com](https://www.tsurugajo.com/tsurugajo/)
## 红瓦与天守博物馆

展览中介绍天守红瓦的实物与说明
红瓦是鹤城很有辨识度的一部分,与当地寒冷气候下的瓦作技术和幕末外观有关。 今天的屋顶经历了复原工程,不能只凭颜色把整座天守都当作江户时代原物。

介绍与会津有关人物的馆内展示
天守博物馆展示城郭、历代领主与会津战争等内容,高处还可眺望市区和盆地。 展览会随企划与更新调整,照片里的楼层说明属于当时的参观记录。
这次的历史展示,让我们把城郭从一个地标,慢慢看成与城市兴衰相连的空间。 想看特定主题时,可以先查馆方的当前展期。
## 茶室麟阁与一碗抹茶

茶室麟阁的入口木门
麟阁与千利休之子千少庵及蒲生氏乡的故事相连。 明治时期,茶室由森川家保存,1990年再移筑回城内。 参观时,可以把这段保存经过与眼前的建筑一并认识。

这次在茶席品尝的抹茶与日式点心

茶席上的会津漆器与茶器
这次恰逢黄金周活动,我们在本丸附近的临时茶席,品尝了宫崎先生点的抹茶。 当地的器物、点心与这一场相遇,留下了很具体的记忆。
这属于当时的特别活动,平日的呈茶与麟阁参观各有安排。 想喝茶的话,可以先看当前的接待方式,不必预设每次都会遇到同一场活动。
[茶室麟阁:保存历史与参观安排www.tsurugajo.com](https://www.tsurugajo.com/tsurugajo/rinkaku/)
## 绘付体验与石垣里的发现

亲手绘制的会津不倒翁小法师
鹤城会馆有不倒翁小法师、赤牛等绘付体验。 它们承载着地方祈愿与吉祥寓意,也能做成旅行里的小纪念。 材料、费用和所需时间,应以当前体验说明为准。

石垣中形状让人联想到小鸟的一块石头
当地朋友还提醒我们看看石垣里形似小鸟或心形的石头。 寻找形状很有趣,也可以顺便观察不同位置的砌筑方式。 石垣在历史中经历过修理和整备,与现代重建的天守各有自己的保存过程。

护城河上的小船活动

记录2024年游船安排的现场告示
照片里的游船属于2024年指定日期的活动。 若想再体验,出发前应另外查询本年度是否举办、怎样预约以及天气限制。
## 从车站前往
从东京方向,可乘东北新干线至郡山,再换磐越西线前往会津若松。 抵达后,利用市内周游公交或其他交通前往鹤城一带。
车站、公交停靠点与城内入口之间仍需要步行。 安排回程时,也给博物馆、茶席和手作之间的移动留下余量。
### 东京站的夜晚
URL: https://www.sakashita.page/tokyo-station-at-night/
Last updated: 2026-10-24T07:09:59.000Z
东京站的夜晚,最适合从丸之内一侧慢慢看。 红砖站房亮起灯以后,前方广场与后面的高楼形成了不同的尺度:一边仍不断有人赶车,一边又让人想停下来,把建筑从头到尾看一遍。
## 先分清车站的两面
丸之内站房面向皇居方向,八重洲则在铁路另一侧。 如果目的是看红砖建筑与站前广场,出站时先认准丸之内方向,比走出任意一个闸机以后再绕路更省事。 站内通道、商业区域与地下出入口很多,约人见面时最好说清具体出口和地上、地下的位置。

夜晚东京站丸之内站房与周围高楼
2024年发行的新版一万日元纸币,背面使用了东京站丸之内站房的图案。 拿纸币上的正面视角与眼前建筑比较,也是一种观察屋顶与立面的方法,不必因此把这座仍在使用的车站只看成一件静止的纪念物。
## 建筑留下的几次变化
东京站于1914年开业,丸之内站房由辰野金吾主持设计。 1945年的空袭烧毁了屋顶与三楼等部分,战后以两层的形态修复。 2003年,丸之内站房被指定为重要文化财;2007年开始的保存与复原工程,在2012年完成。 这些变化可在[车站的官方历史介绍](https://www.tokyostationcity.com/learning/)中连起来看。

灯光映照下东京站的红砖外墙
今天的外观既保留历史材料,也包含后来的修复与复原。 看红砖与白色线脚时,不必给它附上“仿造阿姆斯特丹车站”这样的未经证实的起源故事。 建筑本身的比例、细部和使用历史,已经足够值得停留。
## 圆顶里的装饰

东京站圆顶内的八角形装饰与天窗
走进南北圆顶空间,抬头可以看见鹰、花形装饰与生肖浮雕。 生肖并没有十二种全部排齐,而是按方位使用其中八种。 [站房细部的官方介绍](https://www.tokyostationcity.com/learning/station%5Fbuilding/)还解释了装饰与复原材料,想仔细看的话,可以先读一点背景,再去找对应的位置。
这里同时是日常通行空间。 拍照时靠边停留,不要挡住出入口,也不要为了仰拍一直停在来往人流中间。
## 车站里的酒店与屋顶
东京站大饭店于1915年开业,与站房一起经历了破坏、修复与重新开放。 它与文学的关系、历次变化,可见[酒店的历史记录](https://www.tokyostationhotel.jp/history/)。 文中介绍的是建筑与周边观察,不是一次住宿评测,也不意味着看圆顶或站房必须订房。

灯光下东京站丸之内站房的屋顶轮廓
从室内再回到广场,屋顶轮廓与两端圆顶便更容易辨认。 与其急着替每一种曲线贴上异国风格的标签,不如换几个距离,看它们怎样连成一条完整的立面。
## 从车站走进丸之内

原旅行记录中的丸之内冬季彩灯
站前广场、行幸通与丸之内街道,可以接成一段不长的夜间散步。 季节彩灯则属于另外的活动安排,照片记录的是当时的景象,不是全年都有的照明。 如果准备继续走向皇居或银座,也先看好返程入口,给回到站台的时间留一点余量。
### 樱花季的镰仓:八幡宫、大佛与湘南海岸
URL: https://www.sakashita.page/kamakura-cherry-blossoms/
Last updated: 2026-10-24T06:59:59.000Z
春天的镰仓,适合把古都散步和湘南海岸放在同一天。 先从车站走向鹤冈八幡宫,再乘江之电去长谷,傍晚把时间留给海边。 樱花的花期每年不同;即使没赶上满开,参道、池塘和海景也各有看头。
## 从若宫大路走向鹤冈八幡宫
鹤冈八幡宫的起源可追溯到1063年,源赖义将石清水八幡宫的神灵迎请到镰仓。 1180年,源赖朝把神社迁到如今的位置,此后这里成为镰仓城市布局的重要中心。

通向鹤冈八幡宫的鸟居与段葛参道
若宫大路的中央有一条高出路面的参道,叫作段葛。 道路两侧的店铺适合慢慢看,进入段葛后,视线便自然转向前方的鸟居。

段葛参道两侧的春日樱花
春天,樱花沿参道展开。 可以先在二之鸟居附近看看整体景色,再顺着人流往八幡宫走;花开得好时,这一段往往比预想中更费时间。
### 太鼓桥与源平池

鹤冈八幡宫的石造太鼓桥
过了三之鸟居,正面就是石造太鼓桥。 桥体不供普通游客通行,参拜时从两侧绕行。

旗上弁财天神社旁的源平池与樱花
源平池旁有旗上弁财天社。 岸边的樱花和池中的倒影,比一路赶到本宫更值得停留。 这里夏季还能赏莲,春天看到的则是另一种景色。

鹤冈八幡宫本宫的朱红色楼门与匾额
登上石阶,可以近看本宫建筑。 匾额上的“八”字呈现出两只鸽子的形状,是这里很有辨识度的细节。
从JR镰仓站或江之电镰仓站步行前往八幡宫,途中都能接上若宫大路。 祭典或樱花季的人流较多,参道和石阶请慢慢走。
[鹤冈八幡宫官方网站www.hachimangu.or.jp](https://www.hachimangu.or.jp/)
## 池畔的镰仓文华馆

池水与樱树旁的镰仓文华馆鹤冈博物馆
镰仓文华馆鹤冈博物馆沿用了原神奈川县立近代美术馆的旧镰仓馆建筑。 这座由坂仓准三设计的建筑于1951年开馆,2019年以新的文化设施身份重新开放,2020年成为日本国家重要文化财。
从池边看,白色建筑、支柱与水面构成了很清爽的画面。 如果对建筑和镰仓文化感兴趣,可以把这里安排在八幡宫之后。 展览内容、票价及换展休馆日随展期变化,出发前查看[馆方的当前展览](https://tsurugaokamuseum.jp/tanoshimu/)。
## 长谷:高德院的镰仓大佛
从镰仓站乘江之电到长谷站,再步行前往高德院。 国宝铜造阿弥陀如来坐像,也就是人们熟悉的镰仓大佛,始铸于1252年。

高德院镰仓大佛与境内石碑
大佛最初安置在佛殿内,后来成为今天露天端坐的形态。 走近时,可以留意佛像的衣纹、面容和周围遗留的础石。
院方另设大佛胎内参观,能从内部观察铸造结构。 胎内参观与境内参拜的开放时间不同,高温等情况也可能影响受理,请以[高德院当天的公告](https://www.kotoku-in.jp/)为准。
## 镰仓高校前:江之电与大海
江之电镰仓高校前站附近的道口,因为《灌篮高手》电视动画片头中的场景而受到许多读者喜爱。 海、铁轨和绿色电车组合在一起,本身也是很有湘南气息的画面。

经过镰仓高校前站附近道口的江之电列车
这里是日常使用的铁路道口,周围也是居民生活的街区。 拍照时留在人行区域,听从现场引导,给车辆、行人和列车让路。

从道路旁望向江之电道口与湘南海面
不必执着于复刻一个机位。 如果道口周围拥挤,可以沿海岸继续散步,把电车和海景放进更从容的行程里。
## 稻村崎:把傍晚留给海岸

稻村崎海滨公园的雕像与夕阳
稻村崎是这条海岸路线中适合收尾的一站。 天气通透时,可以眺望江之岛和富士山;有薄雾的日子,海面与远处的山影也很柔和。

薄雾中从稻村崎眺望江之岛与富士山
从江之电稻村崎站步行到海边,再按日落时间安排停留。 海岸风大,春天傍晚也需要一件挡风的外套。
### 稻村崎温泉

稻村崎温泉与餐厅所在的建筑
海滨公园对面的稻村崎温泉,是散步后休息的另一种选择。 馆内有公共浴场和需要预约的家族风吕,两者的使用条件不同。

稻村崎温泉附近走廊边的小地藏
如果计划泡汤,先确认年龄限制、预约方式与最晚受理时间。 温泉行程可以和海边散步搭配,但要给返程电车留出时间。
[稻村崎温泉官方网站www.inamuragasaki-onsen.jp](https://www.inamuragasaki-onsen.jp/)
## 一天怎样安排
- 🚶上午:镰仓站出发,沿若宫大路和段葛前往鹤冈八幡宫,视展期加入镰仓文华馆。
- 🚃中午至下午:乘江之电到长谷,参拜高德院,再沿线选择海边停靠点。
- 傍晚:在稻村崎散步看海;若加入温泉,就减少白天的停靠站。
樱花季的镰仓容易拥挤,一天保留两三个重点,通常更容易享受沿途的街景。
### 春日富士山:河口湖与新仓山浅间公园
URL: https://www.sakashita.page/mount-fuji-and-cherry-blossoms/
Last updated: 2026-10-24T06:49:59.000Z
春天从山梨一侧看富士山,山麓的樱花与山顶的积雪会进入同一幅景色。 这组旅行照片里,有河口湖的水面、大石公园的停留,也有新仓山浅间公园的石阶与忠灵塔。 它们是几种不同的看山方式,可以按天气、花期与交通安排取舍。
## 从山麓看富士山

新仓山浅间公园樱花间的富士山
富士山跨越山梨、静冈两县,最高点海拔3776米。 赏樱与湖边散步主要发生在山麓,不需要把登顶安排进同一趟春日行程。 山顶是否露出、云层怎样变化,也会影响眼前的景色,给停留时间留一点余量即可。
富士五湖位于山梨一侧,分别是山中湖、河口湖、西湖、精进湖与本栖湖。 它们的位置、交通与湖岸景观各不相同,第一次来访时,可以先选一两处慢慢看。

湖畔远眺白雪覆盖的富士山
## 河口湖:把湖岸作为散步路线

河口湖
河口湖周边有公园、美术馆、餐饮和住宿,适合以车站为交通起点,再选择一段湖岸散步。 如果租自行车,要先看路线距离、天气与返还地点;若乘巴士,就把站点和返程班次一起记下来。 湖很大,地图上同属河口湖周边的两个地点,实际并不一定适合短时间内步行往返。
## 大石公园与自然生活馆

大石公园河口湖自然生活馆

大石公园河口湖自然生活馆二楼餐厅
大石公园在河口湖北岸,是把湖面与富士山一起收入视野的停留点。 自然生活馆与附近商店,让散步之外也有坐下来休息、看看土产的选择。 照片里留下的是当时的馆舍与餐厅,之后来访时,可以按当天开放的空间安排用餐和休息。
如果主要目的是看山,不必把所有拍摄角度都排进清单。 沿湖走一小段,看看花木、岸边与山的位置怎样变化,同样能感受到这一侧的风景。
## 新仓山浅间公园:从石阶走到忠灵塔

富士浅间神社的石阶与灯笼
新仓山浅间公园位于富士吉田市。 从山脚走向高处,会经过台阶与坡路,观景处的忠灵塔、街区与远处富士山,共同构成了这里熟悉的景象。 安排时间时,要把上坡、停留和下山都算进去,而不只计算从车站到公园入口的距离。

新仓山浅间公园的忠灵塔
春天来访,重点自然在樱花;其他季节也可以看看山坡与城市的层次。 开花进度、园区利用与交通管理,可以从[富士吉田市的公园页面](https://www.city.fujiyoshida.yamanashi.jp/page/13101.html)和观光网站确认。

2024年3月30日至4月14日的新仓山浅间公园樱花祭宣传册
照片中的宣传册属于2024年的活动。 富士吉田市已公告,2026年春不举办新仓山浅间公园樱花祭,因此看樱花与参加节庆需要分别确认。 后续年度的活动和花期运营安排,应查看[当地观光网站的公告](https://fujiyoshida.net/feature/index.php?p=sakura)。

从山坡俯瞰街道、樱花与富士山
## 交通与行程取舍
- 🚃 从东京方向出发,可比较新宿直达特急,以及在大月换乘富士急行线的方案。
- 河口湖周边的行程,以河口湖站和湖岸巴士连接各个停留点。
- 🚶 前往新仓山浅间公园,可从下吉田站步行,再按现场路径上坡。
直达列车与普通列车的停站、座位安排和所需时间不同,不能只用一段固定车程来估算整趟旅行。 [富士河口湖观光网站的铁路交通页](https://fujisan.ne.jp/pages/31/)提供了铁路公司的查询入口。 若同时安排河口湖北岸与新仓山,可以选一处作为主要停留;住一晚的话,就更容易给天气和拍照留出弹性。
登顶富士山是另一套行程,需要按所选路线确认开放期、装备、预约与入山安排。 若另有登山计划,请使用[富士登山官方网站](https://www.fujisan-climb.jp/)的当年信息,把它与山麓赏樱分开准备。
### 东京赏樱散步:五处花景与花期判断
URL: https://www.sakashita.page/tokyo-cherry-blossoms/
Last updated: 2026-10-24T06:39:59.000Z
东京的樱花不只在一座公园里。 上野适合在树下慢慢走,千鸟渊的花枝伸向护城河,隅田公园能把樱花与晴空塔放进同一幅画面。 芝公园、增上寺和目黑川,则让赏花接上寺院、街区与河岸的日常风景。
这里整理了五处赏樱地点,也保留了2024年的照片。 安排下一次旅行时,先看当年的花况,再挑一两处相近的地方,比一天赶遍所有名所更从容。
## 先认花,再看花期

春日枝头绽放的红白花朵
日本的樱花并非只有一种,野生种与园艺品种在花色、花形、树姿和开花时间上各有差异。 因此,同一座公园里也可能有的树已经落花,有的才刚开。 看一张花况照片时,要把拍摄日期和品种一起考虑。

河岸边伸向水面的白色樱花
街头常见的“染井吉野”是一个园艺品种。 它是通过嫁接等方式繁殖的栽培品种,源自同一个克隆系;森林综合研究所的[研究说明](https://www.ffpri.go.jp/press/2017/20170118/documents/20170118press.pdf)区分了这个栽培品种与范围更广的种间杂种名称。 染井吉野具有自交不亲和性,其结实情况仍与花粉来源和具体个体有关。 日本植物生理学会的[问答](https://jspp.org/hiroba/q%5Fand%5Fa/detail.html?id=6165)解释了这种区别。
气象厅所说的“开花”和“满开”,也有特定的观察标准:
- 开花日:标本树第一次有五至六朵以上的花开放的日期。
- 满开日:标本树第一次达到约八成以上花朵开放的日期。
这两项是标本树的观测结果,并不表示整座城市、所有品种在同一天达到相同状态。 可以先查[气象厅的观测说明与资料](https://www.jma-net.go.jp/tokyo/shosai/kanyoho/sakura/sakura.html),再配合各公园发布的现场花况。 [tenki.jp的樱花专题](https://tenki.jp/sakura/)提供的预测与花况信息,也应与气象厅的观测记录区分。 东京常见的赏樱时段在3月下旬至4月上旬,但具体日期受年份、品种与天气影响,不能固定成“每年4月第一周”。
## 上野恩赐公园:树下散步,也能接上博物馆

上野公园步道旁盛开的樱花
上野的赏樱历史与宽永寺有关。 台东区的[介绍](https://www.city.taito.lg.jp/event/kanko/uenosakuramatsuri.html)记载,创建宽永寺的天海僧正曾从吉野山移植樱花到这里。

上野恩赐公园里的另一处樱花景色
公园周边聚集了东京国立博物馆、国立西洋美术馆、国立科学博物馆和上野动物园。 如果遇上雨天,或想暂时离开赏花人潮,可以选一处展馆参观;展览与休馆日要分别确认。

上野至御徒町之间的阿美横丁
赏花后若往御徒町方向走,可以顺路逛阿美横丁,找地方吃饭。 这段街区与公园的气氛不同,适合作为散步的后半程。
🚃 以JR、东京地铁上野站或京成上野站为起点,按想进入的公园区域选择出口。
## 千鸟渊:沿着护城河看花

花枝垂向护城河的千鸟渊
千鸟渊绿道位于皇居西侧,沿护城河延伸约700米。 这里的看点是水面与花枝的层次:站在步道上看,是树梢向河面展开;小船经过时,画面又多了缓慢移动的线条。 乘船是另一种赏花方式,但不必把排队坐船当成散步的必选项目。
🚃 可从九段下站或半藏门站步行前往。 [千代田区观光协会的绿道说明](https://visit-chiyoda.tokyo/app/spot/detail/446)列有出入口、步行路线和船场信息。 赏樱期间的通行安排、夜间照明与船场营业,应看当年的公告。
## 隅田公园:樱花、河流与晴空塔

隅田公园的樱花与河岸风景
隅田公园沿隅田川两岸展开,和上野的林荫步道、千鸟渊的护城河又是不同的空间。 在浅草一侧散步,可以隔着河面看东京晴空塔;换到另一侧,视角也会随之改变。 若本来就计划逛浅草,把河边赏花接在后面很方便。
🚃 可从浅草站或本所吾妻桥站步行到河岸,再按当天想走的一侧安排路线。 [日本国家旅游局的隅田公园介绍](https://www.japan.travel/en/spot/1688/)可作为认路的起点。
## 芝公园与增上寺:在东京铁塔旁看春天

芝公园里的樱花

增上寺境内的樱花
这一带适合把赏花与寺院参拜放在同一段步行里。 抬头能见到东京铁塔,近处则是寺院建筑与树木,远近之间很有变化。
芝公园于1873年开园,是日本早期设立的公园之一。 不过,今天的增上寺境内与都立芝公园应分开理解;公园管理方的[历史说明](https://www.tokyo-park.or.jp/park/siba/)记载了战后寺院境内从公园区域划出的过程。 参观寺院时,也要留意宗教活动、摄影限制,以及收费展室与户外区域的区别。
🚃 御成门站、芝公园站、大门站和滨松町站都可作为这一带的步行起点。 选择哪一站,取决于先去寺院、铁塔还是公园。
## 目黑川:选一段河岸慢慢走

目黑川上方交错盛开的樱花枝
目黑川的樱花常常从两岸伸向水面,桥上与河边步道看到的景色各有不同。 河流经过多个街区,“去目黑川”并不是只有一个入口,也不必从头走到尾。 中目黑周边适合看花枝覆盖河面的景色;往下游走,河道与步道的空间又会变化。
🚃 中目黑站适合进入中目黑一带;从目黑站出发,则可安排太鼓桥附近的河岸散步。 出发前看[目黑区观光协会的赏樱地图](https://meguro-kanko.com/map/pdf/Sakura%5FMap%5F2024J.pdf),先认清想走的河段。 这份地图是2024年版本,其中活动日期不适用于之后的年份。
## 怎样组合一天的行程
- 想少换车:上野公园与阿美横丁,或浅草与隅田公园,各自就能组成一段完整的散步。
- 想看水边樱花:千鸟渊与目黑川选一处作为主行程,把时间留给步行和停留。
- 想兼看城市建筑:芝公园、增上寺与东京铁塔周边适合放在一起。
赏花时不要折枝、摇树,也不要踩进围护树根的区域。 拍照时给行人留出通道,河岸与住宅区尤其要避免停在桥中央或私人出入口。 饮食、席地停留与垃圾处理都以现场规则为准,把花和空间留给下一位经过的人。
### 漫步仓敷美观地区
URL: https://www.sakashita.page/kurashiki-bikan-quarter/
Last updated: 2026-10-24T06:30:00.000Z
仓敷美观地区适合把步行速度放慢。 河道、白墙与柳树形成最容易认出的街景,往小巷里走,又会遇见町家、博物馆、工艺店与红砖工厂改建的空间。
这里的历史与米、棉花和纺织业相连。 沿河看仓库,再走进商家住宅,最后到常春藤广场,就能把几种不同用途的建筑串起来。
## 沿着仓敷川认识街区

仓敷川上的天鹅与两岸白墙建筑

仓库、柳树与河道相连的街景
江户时代,仓敷是物资集散地,商家与仓库围绕河道发展。 进入近代后,西式建筑和产业设施又加入其中,形成今天可以步行阅读的城市层次。
“美观地区”保留下来的,是建筑群与街道共同构成的环境。 从桥上看水面,再沿两岸换一个角度,比只在一个拍照点停留更容易理解它的布局。
如果想乘小舟,应另查当日售票、天气与运行安排。 河边仍是居民和店家使用的街道,拍照时也留出通行空间。
## 仓敷考古馆

仓敷考古馆的格子窗与白墙

考古馆内的埴轮造型与文物介绍
仓敷考古馆于1950年开馆,利用江户时代的米仓改建。 馆内介绍吉备地区的考古资料,时间范围比眼前的江户街景更久远。
从旧仓库走进石器、土器与古坟时代的展示,也能把目光从商业城市延伸到这片土地更早的生活。 开馆日期并非每天相同,想入馆应先查当前日历。
[仓敷考古馆:展览与参观信息www.kurashiki-tabi.jp](https://www.kurashiki-tabi.jp/see/see-1208/)
## 两座商家住宅

井上家住宅的木格栅与入口

井上家住宅的传统町家外观
井上家住宅是美观地区具有代表性的大型町家,也是日本国家重要文化财。 看住宅时,可以留意面向街道的店铺部分与后方生活空间怎样连接。 公开区域与导览安排,应按住宅当前公告参观。
[井上家住宅www.inoueke.jp](https://www.inoueke.jp/)

旧大原家住宅的白墙与木门
旧大原家住宅以“大原本邸”的名称对外开放。 建筑从1795年开始营建,之后逐步增建,到明治中期形成接近今天的样子。
大原家的商业、纺织业经营与文化事业,能够在这里连成一条线。 走过住宅与仓库之间的空间,也可以看出一座商家宅邸如何同时容纳经营和生活。
[大原本邸:建筑与参观安排www.oharahontei.jp](https://www.oharahontei.jp/)
## 帆布、牛仔布与和纸胶带

店铺门前陈列的彩色帆布包

美观地区的蓝染与牛仔服饰店

店内陈列的蓝染服装与牛仔裤
仓敷与周边地区有深厚的纺织产业背景,散步时会遇见帆布包、牛仔服饰与蓝染商品。 帆布和牛仔布有不同的织物结构,挑选时可以实际看看厚度、触感与缝制细节。
儿岛地区也是认识当地纺织业的重要地点,但与美观地区之间需要另外乘车。 如果想逛工厂、品牌店或牛仔街,适合另留一段行程。

文具店里各式图案的和纸胶带

仓敷和纸胶带的商品展示

以彩色胶带装饰的迷你汽车
和纸胶带也是这里很容易发现的伴手礼。 当地企业将原本偏工业用途的胶带,发展成文具和杂货产品;“mt”于2008年开始销售的彩色胶带,是这段变化的重要一页。
小卷胶带可以用来装饰笔记、包装或卡片。 挑几种自己会用的花纹,也能把街头看到的颜色带回日常生活。
[仓敷观光:和纸胶带的发展www.kurashiki-tabi.jp](https://www.kurashiki-tabi.jp/rm%5Fbuy/rm-buy189/)
## 常春藤广场:旧工厂的新用途

常春藤广场的红砖步道

常春藤攀附的砖墙与建筑入口

中庭水池与红砖建筑
常春藤广场的前身,是1889年建成的仓敷纺绩本社工厂。 1973年再开发为综合设施后,旧工厂与住宿、展示、工艺体验等用途相结合。
从河边的仓库走到这里,建筑材料与空间尺度都改变了。 如果想理解仓敷从物资集散到近代纺织工业的发展,这一段很值得放进行程。

白桃与葡萄口味的冈山伴手礼

常春藤广场店内的当地糕点
店里的白桃、葡萄口味商品,也提醒人们仓敷所在的冈山有另一种季节风味。 购买时看清商品种类和保存方式,挑适合带回去的即可。
[仓敷纺绩:工厂与常春藤广场的沿革www.kurabo.co.jp](https://www.kurabo.co.jp/company/chronology.html)
## 桃太郎与冈山的旅行形象

桃太郎与伙伴们的传统玩偶
桃太郎的故事在日本流传很广,冈山也经常用它介绍地方形象与伴手礼。 常见版本里,老夫妇从桃子中遇见孩子;长大的桃太郎带着团子出发,与狗、猴和雉鸡结伴前往鬼岛。
这是民间故事,有不同版本与地方联系。 旅行时看见玩偶、团子或相关包装,可以把它们当作认识地方叙事的线索。
## 怎样抵达与取舍
从东京、大阪等地,可先乘新干线至冈山,再换JR山阳本线到仓敷站。 从仓敷站步行前往美观地区,通常比较容易安排。
如果只有半天,可以沿河道散步,选择一座住宅或博物馆,再到常春藤广场。 想认真看展、参加体验或逛店,则留出一天,把午餐和休息也放进行程。
### 福冈与太宰府天满宫之旅
URL: https://www.sakashita.page/fukuoka-and-dazaifu/
Last updated: 2026-10-24T06:19:59.000Z
福冈的旅行可以从海边的开阔景色,慢慢走进天神的老建筑,再延伸到太宰府的参道。 这趟旅行把福冈塔、红砖文化馆、太宰府天满宫与博多运河城串在一起,既看城市,也留一点时间吃甜点。
太宰府位于福冈县太宰府市,与福冈市区需要乘车往返。 若想在天满宫和参道多逛一会儿,可以把它单独安排半天,将市区景点留到另一天。
## 福冈塔:先从海边认识城市

福冈塔吉祥物的拍照看板

福冈塔内的纪念拍照区
福冈塔高234米,最高的展望室位于地上123米。 从这里可以环视博多湾、海滨街区与远处的山地,晴天适合看海岸线,傍晚则能等城市灯光渐渐亮起。
塔内的展望空间分布在116米、120米与123米三个高度,分别设有拍照区域、餐饮空间和观景楼层。 若计划在塔内用餐,应另外确认餐厅的营业时间与订位规则。
福冈塔所在的百道滨与博多站、天神之间都有一段距离。 乘公交时可先查福冈塔方向的班次;选择地铁,则要把西新站以后的步行或换乘时间算进去。
[福冈塔:楼层、门票与交通www.fukuokatower.co.jp](https://www.fukuokatower.co.jp/)
## 红砖文化馆:天神街头的近代建筑

红砖与白色石材相间的福冈市红砖文化馆
红砖文化馆原为日本生命保险九州支店,由辰野金吾与片冈安设计,1909年竣工。 红砖配白色石材,加上尖塔与圆顶,在街头很容易认出来。
这座建筑于1969年被指定为日本国家重要文化财,后来曾用作市历史资料馆。 1994年恢复早期室内装饰后,以红砖文化馆的名称重新开放。

文化馆内的钢琴、窗帘与室内装饰

旅行时拍摄的文化馆咖啡吧台
地址是福冈市中央区天神一丁目15番30号,从天神地区步行前往比较方便。 楼上的会议室有独立的使用安排,室内能参观到哪些空间,要看当天的开放情况。
[福冈市文化财:红砖文化馆bunkazai.city.fukuoka.lg.jp](https://bunkazai.city.fukuoka.lg.jp/sp/cultural-properties/detail/51)
## 太宰府天满宫:参道尽头的学问之神

天满宫境内悬挂的粉红色祈愿纸签
太宰府天满宫供奉菅原道真,人们将他尊为学问、文化与艺术之神。 考试前来祈愿,是这里广为人知的参拜习俗。
菅原道真是平安时代的学者与政治人物,晚年被贬至太宰府,并在当地去世。 有关雷电、怨灵与飞梅的故事,则属于后来流传的信仰叙事。 带着这些背景走过参道,能更容易理解境内的梅树、御神牛与各种祈愿物。

太宰府天满宫境内的手水舍
本殿的修缮也会改变参观动线。 神社已于2026年9月19日恢复本殿正面的参拜,旧游记里出现的临时殿与施工围挡,可能与再次到访时的样子不同。
### 怎样前往太宰府
- 🚃 从天神出发:在西铁福冈(天神)站乘天神大牟田线,到西铁二日市站换乘太宰府线。
- 🚌 从博多巴士总站或福冈机场国际线航站楼出发:查询开往太宰府方向的直达公交。
- 🚶 抵达太宰府站后,沿参道步行前往天满宫。
机场的直达公交与西铁电车是两种不同的交通方式。 选择路线时,按当天的出发地点比较班次和换乘次数即可。
[太宰府天满宫:交通与参拜安排www.dazaifutenmangu.or.jp](https://www.dazaifutenmangu.or.jp/en/plan/getting-to-dazaifu-tenmangu/)
## 回到博多,在运河城吃点甜的

旅行途中品尝的冰淇淋
这趟旅行里,Itoshima Farm House UOVO的甜点让我留下了不错的印象。 照片记录的是当时吃到的冰淇淋,想再去同一家店时,可以先查运河城目前的店铺目录。

福冈商业设施内的建筑与购物空间

博多运河城的彩色建筑与中庭
博多运河城把商店、餐厅与中庭水景连在一起,逛累了可以就近找地方吃饭。 拉面竞技场里有不同店家的口味,喷泉和投影节目则有各自的演出时刻表。 若特别想看某场节目,先核对当天日程,再决定用餐顺序。
交通上,从博多站步行约10分钟,或从地铁七隈线栉田神社前站步行约3分钟,都比较容易安排。 傍晚回到这里,可以把购物、晚餐与水景留在同一个区域慢慢逛。
[博多运河城:店铺、节目与交通canalcity.co.jp](https://canalcity.co.jp/)
### 有马温泉的节分与变装活动
URL: https://www.sakashita.page/arima-setsubun-festival/
Last updated: 2026-10-24T06:09:59.000Z
2024年的这次有马温泉旅行,正好遇上节分之日,温泉街里也有角色扮演活动。 熟悉的动画角色出现在桥边、小路与足汤旁,让原本以泡汤为中心的行程,多了一段意料之外的街头风景。
## 宁宁桥与秀吉像

宁宁桥旁的宁宁铜像

有马温泉广场里的丰臣秀吉坐像
从有马温泉站走进温泉街,朱红色的宁宁桥很醒目。 桥名来自丰臣秀吉的正室宁宁,桥旁的宁宁像与河对面广场里的秀吉像相望。 [神户市的介绍](https://www.city.kobe.lg.jp/a64051/arimasengen.html)也把这两尊像作为进入有马的地标。 走到这里,可以先沿河看看两岸,再慢慢进入温泉街。
## 节分:在季节交替时寄托祈愿
现在通常所说的节分,是立春的前一天。 日期并不永远固定在2月3日,例如2025年的节分就是2月2日。 国立天文台的[说明](https://www.nao.ac.jp/news/blog/2025/20250319-equinox.html)解释了节气日期与历法的关系。

以沙丁鱼头和带刺枝叶表示节分习俗的插图
把带刺的柊枝与沙丁鱼头组合起来悬挂,是部分地方节分驱邪习俗中的一种。 它寄托的是借植物的刺与鱼的气味驱赶鬼怪的想法。 林野厅的[植物资料](https://www.rinya.maff.go.jp/kanto/gizyutu/attach/pdf/sikentinosyokubututati-4.pdf)也介绍了柊的带刺叶片与节分用途。

节分时常见的惠方卷与撒豆用品

与节分有关的装饰与习俗
撒豆是另一项常见习俗,常听到的意思是“鬼出去,福进来”。 家庭活动里,有人扮鬼,有人撒豆;不同地方、寺社的做法与口号也可能不同。 吃与年龄相应数量的豆子、朝当年的吉利方向吃惠方卷,同样带有祈愿的意味。 [农林水产省的节分与惠方卷介绍](https://www.maff.go.jp/j/keikaku/syokubunka/culture/wagohan/articles/2301/spe13%5F04.html)整理了这些饮食习俗。
## 温泉街里遇见角色扮演

宁宁桥旁身穿白色角色服装的参与者

温泉街上的吉祥物装扮

在有马温泉街头活动的角色扮演者
当天的角色扮演活动,让温泉街变成了很有趣的拍照背景。 角色服装与木造建筑、河边栏杆放在一起,形成了日常街景里少见的组合。 能认出的角色会让人会心一笑,认不出的装扮,也可以先欣赏服装与造型里的细节。

有马温泉街的坂田银时角色扮演者
角色扮演常以动画、漫画、游戏等作品中的人物为题材。 这次最有意思的地方,是这些形象从作品里走出来,与温泉街里的散步、休息和拍照接到了一起。

温泉街小路上的角色扮演活动

在有马温泉留下的角色扮演照片
这些照片记录的是当次旅行遇到的活动。 以后若想专程参加,应先确认主办方当年的日期、会场与拍摄规则,不能只按节分日期推定每年都有相同安排。

在足汤边拍照的角色扮演者

温泉街上的角色扮演合影
想拍近照或一起合影时,先征得对方同意。 在足汤、店门口与狭窄的小路上,也要给其他游客和居民留下通道。
## 古泉阁与旅行中的伴手礼

元汤古泉阁的庭园与建筑

有马温泉古泉阁的日式建筑
这次记录里还留下了古泉阁的庭园与建筑。 当时的旅馆介绍以自家源泉的金泉为特色,也提到岩风吕与八角堂两处浴场。 这里保留的是2024年的旅行记录;现在安排住宿,可先查[有马温泉官方旅游资料](https://www.feel-kobe.jp/area-guide/arima/)所列的设施与联络方式。

在有马温泉购买的碳酸煎饼

旅馆内摆放的小竹篮花饰

有马温泉旅行中挑选的伴手礼
我们还买了碳酸煎饼、黑蜜栗子小馒头与竹制小花篮。 街头的热闹、旅馆庭园里的安静,以及带回家的小物,构成了这次有马温泉旅行的不同片段。
### 有马温泉经典一日游
URL: https://www.sakashita.page/arima-onsen-day-trip/
Last updated: 2026-10-24T06:00:00.000Z
有马温泉位于兵库县神户市北区。 从神户市区出发,可以把泡汤、温泉街散步和一顿午餐放进同一天;若还想慢慢逛寺院与泉源,住一晚会更从容。 这里最容易辨认的特色,是茶褐色的“金泉”和无色透明的“银泉”。

展示温泉评价历史的明治时期排名榜
街上能看到旧时的温泉排名榜,也能读到与行基、丰臣秀吉有关的故事。 这些材料让人感受到有马长期以来的知名度;至于泉水的性质,则应看各源泉与浴场公布的分析资料。
## 从泉源认识温泉街

有马温泉御所泉源的介绍牌

有马温泉街的木造钟楼

介绍金泉、银泉与足汤的街头标识
金泉含有铁和盐分,泉水中的铁接触空气后氧化,使水呈现茶褐色。 有马温泉观光协会的[金泉源介绍](https://www.arima-onsen.com/pickup/11/533/)列出了天神、御所等源泉,也说明了这个颜色变化。 散步时看看泉源的设备与说明牌,再进入浴场,会更容易把街道和地下涌出的水联系起来。
“银泉”是有马当地对无色泉水的称呼,具体泉质因源泉而异。 金泉、银泉是温泉的分类称呼,金之汤、银之汤则是可以利用的公共浴场,安排路线时要分清这两组名称。
## 金之汤:泡汤与街边足汤

金之汤通往浴场的木制楼梯

有马温泉当地的铁炮水碳酸饮料

有马温泉公共浴场金之汤
金之汤是温泉街里容易找到的公共浴场,建筑外还有足汤。 可以先在街上走一段,再留出完整的泡汤时间;只想在散步途中停留,也可先了解足汤的使用规则。 进入浴场前,确认洗浴方式、随身物品与毛巾的安排,具体要求以设施公告为准。

街上售卖的有马汽水与有马麦酒

旅行时看到的铁炮水碳酸饮料与温泉浴剂
街上的碳酸饮料与温泉浴剂,也是这次旅行留下的记忆。 照片里的标价属于当时的记录,之后购买时请看店内价目。 浴场开放时间、休馆日和金之汤与银之汤的联票,可查[公共浴场官网](https://www.arimaspa-kingin.jp/)。
## 银之汤:把步行留在两次停留之间

有马温泉公共浴场银之汤
银之汤位于温泉街较高的一带,从车站走过去会经过坡路。 它与金之汤的泉水外观不同,若想比较两处浴场,可以把街区散步与休息放在中间。 一日游也可以只选一处,把余下时间留给吃饭、看泉源和逛小店。
金之汤与银之汤的休馆安排并不相同,买联票之前先核对当天能否使用两馆。 [神户市的银之汤设施资料](https://www.city.kobe.lg.jp/a64051/kenko/handicap/syakaikatdudou/barrierfree/shurui/bunka/03-31.html)也列有开放时间与无障碍信息。
## 在温泉街吃饭与散步

店内陈列的签名板

有马温泉街的日式用餐空间

温泉街的釜饭店くつろぎ家
这次记录里的くつろぎ家是一家釜饭店,适合把午餐作为行程中的一次停留。 店家的[官网](https://www.umemaru.net/kutsurogiya/)介绍了鱼介、山菜等釜饭,也说明目前不接受预约、售完即止。 若当天有返程车次要赶,先看候位情况,再决定是否等餐,给其他小店留一点选择余地。

温泉街里的石砌水景与铜像

温泉街里以河童为主题的铜像
街上的河童铜像,是散步时很容易让人停下来的小细节。 河童属于日本的民间传说,不同地方的故事与造型各有变化;在温泉街,它也成了亲切的街头形象。 看过主要浴场之后,沿着小路找这些细节,是有马一日游另一种轻松的收尾。
## 从神户或大阪前往
- 🚃 铁路:先到神户三宫一带,转市营地铁至谷上,再乘神户电铁经有马口前往有马温泉站。
- 🚌 巴士:神户、大阪等地有前往有马温泉的巴士选择,部分为预约制,应同时确认乘车站与返程班次。
铁路在谷上与有马口的衔接,以及巴士的出发站,会影响实际所需时间。 可从[观光协会交通页](https://www.arima-onsen.com/access/)选择自己的出发地区;其中[兵库县内的路线说明](https://www.arima-onsen.com/access/hyogo/)列出了三宫出发的换乘顺序。 抵达温泉街后,以步行连接浴场、泉源和餐厅即可,但别忽略坡路与行李带来的负担。
### 宇治平等院之行
URL: https://www.sakashita.page/byodoin-temple/
Last updated: 2026-10-24T05:49:59.000Z
平等院位于京都南面的宇治。 十日元硬币上的凤凰堂,让许多人在到访之前就已经见过它的轮廓;真正走到池边,建筑、倒影与周围空间的关系才慢慢展开。 这里适合把庭园、堂宇与博物馆连起来看。

宇治的紫式部石像,与《源氏物语》宇治十帖相关的街景
## 先分清建寺与建堂的年份

平等院院门
藤原赖通于1052年将父亲藤原道长的别业改为寺院,次年建立阿弥陀堂,也就是后来称为凤凰堂的建筑。
## 凤凰堂与池水

平等院凤凰堂的朱红色柱廊与屋檐

凤凰堂屋顶上的凤凰,现置于屋顶的是复刻品

跨过池水通向凤凰堂的朱红色桥
凤凰堂由中堂与两侧翼廊、后方尾廊等部分构成,面向东方,隔阿字池表达西方净土的意象。 [寺方的建筑介绍](https://www.byodoin.or.jp/learn/architecture/)说明了建筑与池中小岛的关系。 沿池移动几步,屋檐、廊柱与倒影的组合就会不同,不只正面一种角度值得看。
屋顶凤凰的原件因保存需要收藏于凤翔馆,现置屋顶的是后来制作的复刻。 它们是金属工艺品。

平等院净土庭园中的池水与建筑
## 净土庭园:慢慢看距离
庭园的意义不只是借来远山作为背景。 建筑、水面与观看者的位置,共同把净土信仰化为可以感受的空间。 参观时先绕池看看,再决定是否申请堂内参观,会比较容易把内外关系连起来。

从侧面仰望凤凰堂的屋顶与廊柱
## 阿弥陀如来与云中供养菩萨
凤凰堂本尊阿弥陀如来坐像,为定朝的作品。 其结构采用多块木材组合的寄木造技法,是理解平安时代佛像的重要线索。 周围的云中供养菩萨,有演奏乐器、舞蹈或合掌等不同姿态,让整个堂内不只是中央一尊佛像的展示。
堂内参观另有名额与时间安排。 请先看[寺方当前的参观通知](https://www.byodoin.or.jp/),不要把庭园入场与进入凤凰堂理解成同一项手续。
## 梵钟与凤翔馆

钟楼内悬挂的梵钟复刻品
钟楼内所见也是复刻,原钟收藏于凤翔馆。 看保存下来的原物与室外复刻之间的分工,有助于理解文化财怎样在展示和保护之间取得平衡。

凤翔馆
凤翔馆介绍凤凰、梵钟、云中供养菩萨等寺藏文物,展出内容与部分空间开放可能调整。 具体作品背景可见[寺方的雕刻与工艺介绍](https://www.byodoin.or.jp/learn/sculpture/)。 馆内与堂内都应遵守摄影规定,把注意力留给现场观看。
## 带走一件小物

以凤凰堂为图案的纪念品

以平等院图案设计的日式棉手巾
日式棉手巾既能日常使用,也有人把喜欢的图案作为室内装饰。 不同产品的尺寸与设计各有特点。 若想带走平等院的记忆,可以从图案与自己实际会怎样使用来选择。
从京都或奈良方向乘JR奈良线到宇治站后,仍需步行到寺院;京阪宇治站则在宇治川另一侧。 选好入口与返程,再给池边留一段完整的停留时间,这次宇治之行就能走得从容。
### 看春季公演,体验京都花街文化
URL: https://www.sakashita.page/kyoto-geisha-spring-dances/
Last updated: 2026-10-24T05:39:59.000Z
想认识京都花街的艺能,公开舞台是一个容易进入的起点。 歌声、三味线与舞蹈在同一场演出中展开,比只在街头辨认服装,更能看到艺妓与舞妓平日学习的内容。 这篇从原来关注的2024年春季公演谈起,提供的是选演出的线索,不是当年的售票预告。
## 五个花街,春季常见四场代表公演
京都的五花街是祇园甲部、宫川町、先斗町、上七轩与祇园东。 这里常说的“四场春季公演”,并不意味着京都只有四个花街,也不是四个地区一起在同一座剧场联演。
[五花街官方演出页面](https://www.ookinizaidan.com/)把春季、秋季与联合公演分开介绍。 祇园东的代表性“祇園をどり”属于秋季,选择旅行月份时,也要把这一点分清。
## 上七轩:北野をどり
上七轩的春季舞蹈公演,可以从[歌舞会官网](https://www.maiko3.com/)查当年的场次与会场。 节目不只是一组可以永远沿用的曲名,剧情、舞台安排与票券条件都会更新。 准备去时,先确认自己看到的是哪一年的页面。
## 祇园甲部:都をどり
祇园甲部的“都をどり”有自己的剧场与演出结构。 节目常结合京都风物与文学题材,但不能把某一年与《源氏物语》有关的剧目,当成之后每年固定上演的内容。
[都舞官方网站](https://miyako-odori.jp/)提供当前入口。 若票券带有茶席安排,茶席与正式开演的时间要分别看,不能只按演出开始时间抵达。
## 宫川町:京おどり
宫川町的春季公演使用“京おどり”这个名称。 它与作为舞蹈门类的“京舞”不是可以随意互换的两个标题。 [官方介绍](https://www.miyagawacho.jp/kyoodori/)说明,每年会为公演创作内容,地方艺妓也承担歌声与乐器演奏。
宫川町的会场曾随歌舞练场改修而变化。 2024年的春秋座信息属于当年的安排,订票时应重新确认当前剧场,不沿用旧地址直接出发。
## 先斗町:鴨川をどり

宇治的紫式部石像,与《源氏物语》题材相关的配图
先斗町的“鴨川をどり”把舞蹈剧与纯舞蹈等内容组合在一场演出里。 原来关注的文学题材,可以成为理解某一年创作的入口,却不等于本文照片记录了那场舞台。
[先斗町歌舞会官网](https://www.kamogawa-odori.com/)发布公演与活动信息。 看节目说明时,也可以留意叙事部分与舞蹈部分的节奏差别,而不只是比较服装颜色。
## 选一场适合自己的演出
先看旅行日期,再比较场次、会场、座位与语言辅助。 摄影、迟到入场和儿童入场条件,都应看对应主办方的规定,不把一场的规则套到另一场。
不需要为“看齐四场”而压缩京都的其他行程。 选一场有兴趣的演出,提前读一点背景,给入场与散场后的散步留出时间,就能把舞台上的京都与街道上的京都自然接起来。
### 别府温泉的地狱巡游
URL: https://www.sakashita.page/beppu-hells/
Last updated: 2026-10-24T05:30:00.000Z
在别府,“地狱”指让人联想到炽热大地的温泉景观。 蓝色或红色的泉池、冒泡的热泥与喷气,是来这里观看的内容;这些观赏池并不是供游客下水泡汤的浴池。
这篇把地狱巡游、博物馆与蒸汽料理放在一起,认识温泉怎样进入别府的风景和日常生活。
## 从别府站出发

别府站前张开双臂的油屋熊八铜像
别府站前的油屋熊八像,是抵达时很醒目的一处地标。 他与别府旅游业的发展关系密切,也经常出现在介绍当地近代观光史的资料里。
从车站去铁轮地区,需要再乘公交或出租车。 不同地狱之间有距离,先看地图和班次,可以减少在两个区域之间来回折返。
[龟之井巴士:路线与运行信息www.kamenoibus.com](https://www.kamenoibus.com/)
## 七处地狱怎样安排

温泉街里升起蒸汽的泉源
目前地狱巡游的共通券,包含海地狱、鬼石坊主地狱、灶地狱、鬼山地狱、白池地狱、血池地狱与龙卷地狱七处。 山地狱等其他设施有独立的经营与售票安排,购票时应看清范围。

血池地狱的红色泉池与热气

鬼石坊主地狱的介绍牌

血池地狱与鬼石坊主地狱的景观记录
海地狱一带的几处景点,可以配合步行游览。 血池地狱与龙卷地狱则在另一片区域,通常需要另安排一段交通。
同样是热泉,颜色、热泥与喷发方式各有差别。 看景时可以先读泉池旁的说明,了解颜色与地质、成分之间的关系,而不只是把七处当作集章清单。

灶地狱入口的锅灶与鬼像

灶地狱二丁目冒着热气的景观

海地狱与灶地狱的不同泉池
若想看龙卷地狱的间歇喷发,应把现场等候算进时间。 共同门票与公交票券是不同的商品,展馆或体验也未必包含其中。
[别府地狱巡游:景点、通票与地图beppu-jigoku.com](https://beppu-jigoku.com/)
## 地狱温泉博物馆

地狱温泉博物馆的木质大厅
博物馆让游客沿着一滴雨水的视角,认识地下水、温泉热与铁轮的生活。 展览用影像和空间体验,把平常看不见的地下过程表现出来。

以泉水与地下旅程为主题的展示
馆方以“50年的地下旅程”作为展示叙事。 沿着这条叙事线索,地下水的移动与温泉形成的过程,也更容易理解。

到访时展出的地狱祭服饰
照片里的服饰来自当时的企划展示。 艺术如何表现别府的地热、祭祀与地方形象,也让温泉参观多了另一种角度。 展期已经过去,准备再访时应看当前节目与展示。
[地狱温泉博物馆:展览与利用信息jigoku-museum.com](https://jigoku-museum.com/)
## 地狱蒸工房铁轮

地狱蒸工房铁轮的入口指示

利用温泉蒸汽烹调食材的蒸炉
“地狱蒸”利用温泉蒸汽烹调食材,是铁轮地区很有特色的料理方式。 选好蔬菜、海鲜或其他食材后,按设施指引放入蒸炉,等待蒸熟。
体验包含接待、食材选择、蒸制与用餐,时间不会只等于食材入炉的几分钟。 蒸汽温度很高,取放蒸笼应听从工作人员指导。
食材与蒸炉费用、可否自带食物及预约方式,要看当前设施规定。 想把午餐安排在这里,可以先查当日接待和排队情况。
[地狱蒸工房铁轮的体验信息beppu-tourism.com](https://beppu-tourism.com/experience/jigokumushikouboukannawa/)
## 带一点当地味道回去

当地名菓“烤瘦马”的包装
大分的“瘦马”原本是一种面粉制成的乡土点心,常搭配黄豆粉和糖食用。 照片里的“烤瘦马”则是以此为题材发展的伴手礼,两者的口感和做法不同。
蒸布丁、温泉蛋与其他点心,也可以按当天看到的店铺来选。 购物与用餐都留一点弹性,往往比追着旧菜单逐样寻找更自在。
## 半天还是一天
- 🚌 抵达:从别府站前往铁轮,确认下车点与返程末班。
- 🚶 巡游:先看一片区域,再决定是否乘车去血池与龙卷地狱方向。
- 🍽️ 多留半天:加入博物馆、蒸汽料理与街区散步。
只看几处地狱,和完整巡游再加体验,是不同长度的行程。 把等车、步行和喷发等候放进去,才能在有兴趣的地方多停留一会儿。
### 长崎原爆资料馆与和平公园
URL: https://www.sakashita.page/nagasaki-atomic-bomb-museum/
Last updated: 2026-10-24T05:19:59.000Z
1945年8月9日11时2分,美军投下的原子弹在长崎爆炸。 长崎市资料引用的历史估计,区分了约7.4万名死者与约7.5万名伤者;这是不同的统计项目,也不同于后来持续增补的追悼名录。 关于这些数字的来历与统计限制,可读[市方的原爆被爆者调查报告](https://www.city.nagasaki.lg.jp/heiwa/3090000/3091000/p033162%5Fd/fil/doutai-houkokusyo.pdf)。
原爆资料馆、爆心地与和平公园,是认识这段历史的几个相邻地点。 参观时可以先在资料馆了解事件经过,再走到户外,对照今天的街区和留下的纪念物。
## 从路面电车站开始

长崎路面电车
从长崎站前一带出发,可查看开往赤迫方向的路面电车,在“原爆资料馆”站下车后步行前往馆区。 若先去祈念像一带,也可比较“和平公园”站的步行路线。 站名、乘车方向与要去的具体区域一起确认,会比只在地图里搜索“和平公园”更清楚。
班次、票价和一日券条件,请查[长崎电气轨道的路线资料](https://www.naga-den.com/pages/9/)。 是否需要买通票,可以按当天实际准备搭乘的次数决定。
## 资料馆:从事件与实物读起

长崎原爆资料馆的玻璃穹顶与入口指示

长崎原爆资料馆
资料馆的常设与企划展厅位于地下二层。 展览把被爆前的城市、原爆造成的损害,以及核武器与和平等主题连接起来。 参观时,除了时间线,也可以留意实物的说明:它原来在哪里、怎样受损、由谁保存下来。
例如,[原爆损害展区的介绍](https://nabmuseum.jp/genbaku/tenji/higai/)展示了受损建筑部件、浦上天主堂的相关资料和城市模型。 这些具体物件,让抽象的破坏规模与曾经生活在这里的人和街道联系起来。

资料馆观景平台与展示历史照片的展板
从馆区走到户外,再看被爆后的历史照片,会更直观地意识到眼前街区经历过的变化。 这段参观不必急着接下一个景点,读过展览以后,也可以留一点时间在外面停留。
2026年,馆方公布了分阶段更新常设展览的安排,部分区域在施工期间暂停公开。 当前能看的范围请查[展示更新公告](https://nabmuseum.jp/oshirase/exhibition/),入馆费用、学生优惠、开放时间和语音导览则见[资料馆的参观信息](https://nabmuseum.jp/genbaku/)。 这些安排不宜沿用旧版旅行记录中的价目和时段。
## 和平公园:水与记忆
资料馆、爆心地一带与平和祈念像所在的区域,各有不同的纪念空间。 可以结合[长崎市的公园介绍](https://www.city.nagasaki.lg.jp/page/44706.html)与现场地图,按自己的步行节奏连接它们。

平和之泉
平和之泉以水纪念被爆者,碑文留下了人们在灾难中强烈口渴、寻找水的记忆。 看喷泉时再读碑文,水就不只是公园里的景观,也承载着具体的苦难与追悼。

长崎参观途中看到的洞穴遗迹入口

战时防空洞
照片中的防空洞遗迹,也是参观途中留下的记录。 与资料馆里的城市照片一起看,战时生活与眼前的地形便有了可以对照的痕迹。 具体遗址的经过,仍应读现场说明,区分这一个地点的记录与整个被爆地区的损失。
## 平和祈念像与每年的追悼

平和祈念像
平和祈念像由雕塑家北村西望创作,1955年完成,青铜像高约9.7米。 长崎市的[作者与作品介绍](https://www.city.nagasaki.lg.jp/site/kodomo/83623.html)说明了它的寓意:指向天空的右手提示原爆的威胁,横伸的左手表达和平,面容则为战争牺牲者祈冥福。 理解这些细节以后,再从不同方向看雕像,会比只记住它的体量更有感受。
每年8月9日,长崎都会举行原爆牺牲者慰灵和平祈念式典,并发表和平宣言。 式典、纪念物与资料馆的保存工作,以不同方式延续着对被爆经历的记录。

雕像:生在未来的孩子
离开时,可以把照片里看到的纪念物,与当天读到的一件实物或一段证言联系起来。 这样记住的,会是具体的人与生活留下的痕迹。
### 熊本城的历史与参观体验
URL: https://www.sakashita.page/kumamoto-castle/
Last updated: 2026-10-24T05:09:59.000Z

从石垣下仰望熊本城天守
熊本城值得看的地方,除了高处的黑白天守,还有支撑城郭的石垣,以及震后持续进行的修复。 先在城内看细节,再从市役所眺望全貌,会对它的规模有更具体的认识。
## 从加藤清正的城,到今天的天守
熊本城所在地区在中世纪已有城郭。 后来,加藤清正在茶臼山一带营建规模更大的城郭,于1607年完成熊本城。 1632年,细川忠利入城,此后这里成为细川家的居城。

介绍熊本地区早期城郭的历史展示
1877年西南战争开战前,天守与本丸御殿一带毁于火灾。 今天看到的大小天守,是1960年以钢骨钢筋混凝土结构复原外观的建筑;城内同时还保存着其他时代的遗构与文化财。

熊本城附近的加藤清正铜像

城内广场上的武将拍照看板
熊本城也有“银杏城”的别称。 城中种银杏以备围城时充作食粮的说法,经常出现在有关清正的故事里。 将这些传说与展览中的年代、实物分开来看,更能体会这座城如何被后人记忆。
## 石垣与震后的修复
2016年熊本地震使城内的建筑与石垣遭受严重损坏。 修复后的天守于2021年恢复内部公开,但整个城郭的修复仍在继续。
走近石垣时,可以观察它向上逐渐变陡的曲线,以及不同位置的砌石方式。 这些细节能帮助理解防御设施的构造,也能看出修复需要逐块调查和复原的原因。

复原后的熊本城大小天守
天守的开放范围,与宇土橹等文化财建筑的修复安排各不相同。 目前宇土橹正在进行解体与修复,出发前可查看官方的复旧地图,确认当天能走的路线。
城内步行有高差,也可能需要经过特别见学通路。 若同行者不适合长时间走路,可以先查无障碍路线与电梯的使用方式,再安排参观顺序。
[熊本城:开放范围与复旧情况castle.kumamoto-guide.jp](https://castle.kumamoto-guide.jp/map/)
## 城彩苑:把午餐和历史展示放在一起
樱之马场城彩苑位于熊本城脚下。 这里的熊本城博物馆“わくわく座”用影像和体验展示介绍城的历史,旁边的樱花小路则集中了餐饮与伴手礼店。

城彩苑内的乡土料理店

樱花小路的店铺与街景
如果对城的沿革还不熟悉,可以先看展示,再到城内寻找对应的遗构。 午餐也可以安排在这一带,减少在市区和城郭之间来回赶路。

旅行时拍摄的城彩苑与二之丸方向循环巴士
城彩苑与二之丸广场之间设有循环巴士。 运行时段、停靠位置与临时调整,应以当天的现场及官方公告为准。
## 从市役所看熊本城全景

熊本市役所本厅舍
熊本市役所14层设有免费开放的展望大厅。 从这里能把天守、石垣和周围绿地放在同一个视野里,适合在城内参观前后补看整体布局。

展望大厅内的城郭导览模型

从市役所展望大厅眺望熊本城与绿树
目前通常的开放时间为工作日8:30至22:00,周末与节假日9:00至22:00。 工作日18:00以后,以及周末和节假日,应从南侧入口进入;年末年初及临时检修期间可能关闭。
[熊本市役所14层展望大厅www.city.kumamoto.jp](https://www.city.kumamoto.jp/kiji00332427/index.html)
## 交通与参观顺序
- 🚋 从熊本站出发:乘市电至“熊本城・市役所前”,再步行前往城郭入口。
- 🚌 从机场出发:可查询机场巴士至樱町巴士总站的班次,再步行前往城彩苑一带。
- 🚶 参观顺序:城彩苑、开放的城内区域、市役所展望大厅。
城郭入口、天守入口与电车站之间都需要步行。 购票前先确认开放地图和最后入场时间,尤其要为天守内部展示与上下楼预留余地。
[熊本城官方网站castle.kumamoto-guide.jp](https://castle.kumamoto-guide.jp/)
### 认识日本的城堡
URL: https://www.sakashita.page/japanese-castles/
Last updated: 2026-10-24T04:59:59.000Z
日本的城郭不只有天守。 山坡上的土垒、干堀、石垣、城门与御殿,都是理解城的一部分。 到一座城之前,先分清地形、建筑用途与保存状态,会比只比较天守高低更容易看出它的特点。
## 城与天守,是不同的范围
城郭包含防御、管理、居住与象征权力等多种空间。 天守在近世城郭中十分醒目,但不是每座城都有,也不一定是城主日常起居的地方。 今天只剩石垣与土垒的遗址,同样可以保留丰富的城郭信息。
常见的多层天守主要在16世纪后期至17世纪初的城郭建设中发展。 不能把战国时代所有山城都想象成山顶有一座白色高塔。
## 山城、平山城与平城
这些分类主要帮助我们理解城郭利用怎样的地形,并非评比防御能力的名次。
| 类型 | 观察重点 | 例子 |
| --- | ---------------- | -------- |
| 山城 | 山脊、坡面与道路怎样形成防御层次 | 竹田城遗址 |
| 平山城 | 小山与周边平地怎样组织城郭 | 姬路城、丸龟城 |
| 平城 | 平坦地面上的堀、土垒与建筑布局 | 松本城、名古屋城 |
松本城是平城,不应因为远处有山就归为平山城。 判断时看城郭本身所处的地形,而不是照片背景。 不同资料在个别城郭的分类上也可能有差异,先看当地的遗址与历史说明更合适。
## 天守的层数与用途
从外面看到的屋顶层数,与内部楼层数不一定相同。 例如松本城天守通常称为五重六阶,外观与室内不能用同一个数字简单概括。 [松本城的官方介绍](https://www.matsumoto-castle.jp/about)把结构、御殿遗址与城门分开说明,参观时可以一起看。
## 石垣、土垒与堀
石垣既形成高差,也支撑台地与建筑,但不代表天然具备可靠的抗震保证。 材料、砌筑、地基、排水与维护都会影响保存状态。 土垒和干堀也不是“比较落后、可以略过”的部分,它们常常更直接反映城郭如何使用地形。
护城河也不全部有水。 沿路线看门、桥、转折与高差怎样相接,比孤立地记几个构件名称更容易理解空间。
## 现存、重建与复原,分别来看
现存建筑往往经过多次修理,并不意味着每一块材料从未更换。 重建或复兴建筑则有自己的年代与用途,也不应直接当成战国时期的原物。
例如大阪城现存天守于1931年复兴,现有石垣与古建筑也不能全部归到丰臣秀吉时期。 [大阪城天守阁的官方说明](https://www.osakacastle.net/)介绍了这些层次。 它作为近代博物馆建筑的历史,本身也值得认识。
## 装饰与今天的参观
屋顶上的鯱是鱼身兽首的装饰,名古屋城的金鯱是其中广为人知的例子。 这类象征与传说可以作为文化背景来理解,不是实际防火能力的证明。
登城还要看当前的修理、通行与内部开放范围。 有的天守需要走陡梯,有的遗址主要是山路,先选适合体力的路线,才能把时间留给真正想看的细节。
### 日本的妖怪与神兽
URL: https://www.sakashita.page/yokai-and-mythical-creatures/
Last updated: 2026-10-24T04:49:59.000Z
在日本旅行,河边的塑像、寺院里的面具和城楼屋顶的装饰,常会让人遇到一些似曾相识的形象。 它们有的属于妖怪故事,有的与神明的使者或祈愿有关,不能全部归成同一种“怪兽”。 认识几个常见形象以后,再看各地的传说,也会更容易发现它们之间的差异。
## 河童:水边的传说
河童是想象中的存在,并不是动物学上的两栖动物。 常见形象像个孩子,头顶有盘状的凹处,背上有甲壳,手脚带蹼。 头顶盘中的水、对黄瓜的偏爱,以及与人相扑的故事,都成了辨认河童的线索。
不同地区对河童的外貌、名字和行为有不同说法。 有些故事把它描绘成会把人或马拖入水中的危险存在,也有故事写它报恩、守约或与人往来。 这些是民间叙事的内容,不是有关真实物种的观察报告,更不能据此给各地传说排出所谓最正统的起源。 [《日本大百科全书》的河童条目](https://kotobank.jp/word/%E6%B2%B3%E7%AB%A5-45345)介绍了名称、形象与地域差异。
芥川龙之介的小说《河童》则把这个形象带入现代文学。 读小说、看乡土资料与欣赏今天的吉祥物,是几种不同的接触方式,理解时也要分清作品和传说各自的语境。
## 鬼:不只有一个固定形象
长角、露出獠牙、手持铁棒,是今天常见的鬼形象。 在故事中,鬼可以是恐惧与灾祸的象征;在节分习俗里,人们又会通过撒豆等动作表达驱邪迎福的愿望。 但不同故事和祭礼中的角色并不完全相同,不能只凭一张凶猛的面孔,就判断它的全部意义。
封面采用的是秋田生剥鬼面具的相关配图。 生剥鬼属于有具体地方背景的民俗行事,面具外貌虽然容易让人联想到“鬼”,仍应结合当地的来访神习俗理解。 看民俗展示时,先读它来自哪里、何时使用以及由谁扮演,会比只记住造型更有收获。
## 天狗:山与修行的联系
红脸长鼻、持羽扇的天狗很有辨识度,也有带鸟喙形象的乌天狗。 天狗常与山岳和修验道联系在一起,常见装束与山伏有关,并不是统一穿着武士甲胄。 民间故事里令人畏惧的天狗,与寺院信仰中的天狗,也未必扮演同样的角色。
例如,[高尾山药王院的介绍](https://www.takaosan.or.jp/about/)把天狗置于饭绳大权现眷属的信仰脉络中,并说明了山伏形象与天狗之间的联系。 这里的说法有具体的寺院与山岳信仰背景,适合与泛泛的妖怪图鉴对照着看。 旅行中遇到天狗像时,附近的说明牌往往比一个笼统的“会飞的妖怪”标签更能解释它为何出现在那里。
## 狐狸:动物、故事与神使
狐狸是真实存在的动物,民间故事又赋予它变身等能力。 而在稻荷信仰中,狐狸作为神明使者的地位,需要与稻荷大神本身区分。 [伏见稻荷大社的问答](https://inari.jp/zh-cn/faq/)对此有明确说明:被视为神使的狐狸,并不等于稻荷大神。
因此,看到稻荷神社的狐像,不宜直接说这里供奉的是狐狸妖怪。 同样,故事里的变身狐狸,也不能自动套用成所有稻荷神社的宗教解释。 形象相似,所属的故事和信仰却可能不同。
## 鯱:城楼屋顶上的祈愿
城楼屋顶上翘起尾巴的鯱,常被描述为虎头鱼身的想象动物。 它与镇火、防火的祈愿有关,是建筑装饰与象征的一部分,并不意味着建筑真的不会失火。 名古屋城的金鯱尤其著名,可以从[城方的介绍](https://www.nagoyajo.city.nagoya.jp/guide/kinshachi/)了解其历史与修复经过。
把河童、天狗、狐像和屋顶的鯱放在一起看,乐趣不只在辨认名字。 还可以多问一步:这是一则故事、一个祭礼角色、一种宗教象征,还是建筑装饰? 同一个形象在地方传说、文学和今天的旅游展示中,可能有很不一样的样子。 想进一步查找某个地区的记录,可使用[国际日本文化研究中心的怪异与妖怪传承数据库](https://www.nichibun.ac.jp/YoukaiDB/),沿着地名和文献线索继续阅读。
### 箱根的经典观光景点
URL: https://www.sakashita.page/hakone-day-trip/
Last updated: 2026-10-24T04:39:59.000Z
箱根的旅行,经常需要在登山电车、缆车、公交和船之间换乘。 大涌谷、芦之湖与神社可以串成一条线,宫之下的历史饭店和湖边的关所,则让这条路线多了近代旅游与东海道的背景。
如果当天从东京往返,可以先选山地或湖畔作为重点。 想把几处都慢慢看完,住一晚会更容易留出用餐与换乘的余量。
## 宫之下的富士屋饭店

富士屋饭店内明亮的用餐空间

富士屋饭店的历史建筑
富士屋饭店由山口仙之助于1878年创设,是箱根近代度假文化的重要地点。 不同时期的建筑、室内木雕与庭园,让饭店本身也有值得停留观察的内容。

饭店门前的暖帘与庭木

庭园里的石塔
从小田原或箱根汤本前往宫之下,可以按住宿地点查询登山电车与公交。 如果只去用餐或看建筑,也应先确认预约及可进入的公共空间。
[富士屋饭店:建筑、餐饮与交通www.fujiyahotel.jp](https://www.fujiyahotel.jp/)
## 大涌谷:看火山活动留下的地形

大涌谷裸露的山坡与蒸汽
大涌谷是箱根火山活动形成的地形与喷气景观之一。 裸露的山坡与不断升起的蒸汽,让火山地形变得格外直观。
这里仍受火山活动、天气与气体状况影响。 当天能进入的区域、缆车运行和步道开放,都应先看官方通知,再决定是否上山。

大涌谷黑鸡蛋主题的拍照地标
黑鸡蛋利用当地温泉热制作,蛋壳在相关成分作用下变黑。 “吃一枚延寿七年”是当地广为人知的吉祥传说,也成了黑鸡蛋的旅行趣味。
从宫之下方向出发,可查询经强罗、早云山前往大涌谷的铁路与缆车组合。 若想直接乘公交,应另核对当前是否有合适班次。
[箱根交通:运行与换乘信息www.hakonenavi.jp](https://www.hakonenavi.jp/transportation/)
## 芦之湖与湖上的鸟居

停泊在芦之湖港口的箱根海盗船
从大涌谷乘空中缆车,可接续到桃源台,再按船班前往箱根町港或元箱根港。 湖上交通有不同运营方,乘船前先认清港口和票券的适用范围。
芦之湖与箱根火山活动、山体崩塌和河流受阻形成的地形有关。 在水面看周围山坡,景色与大涌谷裸露的地表很不一样。

从湖上望向岸边的朱红色鸟居
箱根神社湖边的鸟居于1952年建立,1964年加挂吉田茂题写的“平和”匾额。
天气合适时,从船上可以看湖岸与远山。 若特别想拍富士山,应考虑能见度与观看方向,不必把晴天照片当成每天都能遇到的风景。
## 箱根驿传与湖边的博物馆

箱根驿传往路终点与返程起点的标记

箱根驿传纪念跑者像
箱根驿传通常在每年1月2日与3日举行,是大学生参加的长跑接力赛。 湖边的纪念设施与博物馆,介绍赛事历史、选手与接力的故事。
即使平日没有比赛,也能从这些资料认识山路与赛道的关系。 想入馆的话,先查当天开放与展览安排;遇到比赛时,周边交通和观赛规则则需另外确认。
[箱根驿传:赛事与历史www.hakone-ekiden.jp](https://www.hakone-ekiden.jp/)
## 箱根关所:东海道上的检查设施

箱根关所复原的木造大门

关所内的道路与复原建筑
箱根关所是江户时代东海道上的重要检查设施。 今天参观的建筑依据调查与史料逐步复原,整体复原于2007年完成。
走进门、值勤空间与眺望位置,可以具体理解人员检查和交通管理怎样在这里进行。 馆内的说明也介绍了当年的检查对象与通行规则。
[箱根关所:历史、开放与交通www.hakonesekisyo.jp](https://www.hakonesekisyo.jp/)
## 箱根神社与九头龙神社新宫

杉木与绿意环绕的箱根神社社殿
箱根神社的由绪追溯至757年,与万卷上人的传承相连。 穿过杉林与石阶来到社殿前,湖畔的喧闹也渐渐远去。

九头龙神社新宫的龙口手水
境内还可以参拜九头龙神社新宫。 参道、石阶与湖边鸟居之间需要步行,想拍照或慢慢看建筑时,也要算进停留时间。
[箱根神社:境内与历史hakonejinja.or.jp](https://hakonejinja.or.jp/hakone/keidai.html)
## 把换乘留在计划里
- 🚃 山地路线:箱根汤本、宫之下、强罗与早云山之间,按铁路和缆车班次衔接。
- 🚠 大涌谷之后:确认空中缆车运行,再接桃源台方向。
- 🚢 湖畔路线:按实际停靠港,选择关所、驿传博物馆或神社,最后查回程公交。
某段交通停运时,整条环游线也会受到影响。 当天先看运行信息,再缩小景点范围,就能把时间用在最想看的地方。
### 小田原经典一日游
URL: https://www.sakashita.page/odawara-day-trip/
Last updated: 2026-10-24T04:30:00.000Z
小田原适合把车站、城址公园与饮食体验放在一天里。 如果还想住一晚,也可以在车站周边停留,再把箱根安排成下一段行程。 从车站附近的城址逛到海边,再回到站旁用餐,可以串起这座城市的几种风景。
## 从车站开始

小田原站东口的站房与连通平台
东京方向可以比较东海道新干线与普通列车,新宿方向则可比较小田急及JR线路。 快车、特急与新干线的停靠和附加费用不同,订票时按具体班次确认,不只记一个固定车程。
一天的路线可以这样取舍:
1. 🚶 小田原站出发,先逛MINAKA与站旁街区。
2. 步行去小田原城址公园,按兴趣选择登天守或看园内花木。
3. 🚃 下午有余裕,再往风祭的铃广鱼糕博物馆。
4. 回车站用餐或返程,住宿者则把晚间留给休息。
## MINAKA与站旁的停留

小田原站旁MINAKA设施的露台入口

天成园小田原站别馆的接待大厅
MINAKA小田原由塔楼与带有城下町意象的商业区域组成。 其中有酒店、餐饮与其他设施,整体介绍见[MINAKA官网](https://www.minaka-odawara.jp/)。 照片里的酒店大厅,是认识这处设施的一个入口,不等于本文完成了所有客房与餐饮服务的住宿评测。

MINAKA小田原的日式店铺与户外座椅

小田原电车站城下町市场
可以在站旁吃饭、挑选土产,再按开放安排去展望空间。 展望庭园、足汤与酒店浴场的使用安排各有不同,最好分别确认,不因它们位于同一楼里就认为可以任意进入。
## 小田原城:分清北条氏时期与后来的建筑

相关配图:丸龟城天守,供比较日本城郭的外观
小田原城在北条氏统治时期成为关东的重要据点,1590年丰臣秀吉攻城、北条氏降伏以后,又经历不同城主与近世整备。 它的历史不能从一位所谓“小早川远平”的平安时代城主直接起算。 当前遗址与建筑沿革,可从[小田原城官网](https://odawaracastle.com/)继续了解。

小田原城址公园盛开的红梅
今天的天守是1960年复兴的建筑,并非北条氏时期的原物。 登楼可以看展览与周边景色,城址公园的桥、石垣和花木则是另一种步行体验。 天守建筑本身的高度,也不能与所在位置的海拔混为一个“60米高”的数字。

小田原城护城河上的朱红色桥
花开时可以多留意园路。 如果当天天气不适合登高,就把看展与地面散步作为重点,不必为了一个固定机位赶完所有设施。
## 风祭:铃广鱼糕博物馆

铃广鱼糕博物馆内的鱼糕制作过程
去风祭,需要从小田原另接一段铁路或巴士。 以实际使用的站名和运营方路线查班次,不把过去的大致分钟数当成所有列车都相同的行程。
馆内可以了解鱼糕的材料、制作与地方饮食背景。 普通参观与制作体验是不同项目,想动手的话,应先查[铃广的官方设施与体验信息](https://www.kamaboko.com/)。

以鱼糕木板为画布的艺术作品展览
鱼糕板上的作品,让日常食物的包装材料又有了另一种用途。 从制作过程看到小小木板上的画,这段参观不只是在商店买一份伴手礼。
最后回到小田原站,留一点时间取行李或用餐。 把一座城与一种食物看清楚,这一天就有了足够鲜明的记忆。
### 尾道一日散步:山坡、寺院与海边
URL: https://www.sakashita.page/onomichi-day-trip/
Last updated: 2026-10-24T04:19:59.000Z
尾道的街道夹在山坡与水道之间。 海边、商店街和寺院看起来相距不远,走起来却会遇到不少坡路和台阶。 一日游可以围绕千光寺山安排一条上山、下山再回到海边的路线,把时间留给步行与停留。
## 先分清尾道站与新尾道站
想从海边开始散步,通常以JR山阳本线的尾道站为起点。 新干线停靠的新尾道站位于另一处,抵达后还要换乘巴士或出租车。 从广岛方向出发,可以比较在来线方案,以及乘新干线后在三原或新尾道换乘的方案,不能只看车程最短的一段。
[尾道观光协会的交通问答](https://www.ononavi.jp/faq/question.html?qa%5Fid=1)区分了这些换乘方式。 订好去程以后,也把返程站名确认一遍,尤其不要把两个车站当成同一个地点来安排步行。
## 从山上往海边走
一条便于取舍的一日路线是:
1. 🚶 尾道站出发,先沿海边看尾道水道,再往长江口与缆车山麓站方向走。
2. 🚡 乘千光寺山缆车上山,在公园和展望处看市区与水道。
3. 沿文学之小径一带步行,参观千光寺。
4. 按体力选择经猫之细道下山,再走到商店街吃午餐。
5. 下午在本通商店街与海边慢慢散步,回尾道站返程。
如果想认真读文学碑、看寺院建筑或进美术馆,就相应减少其他停留。 也可以选择缆车往返,把体力留给平地的街区;山上的小路并非每一段都适合带大件行李。
## 千光寺山:看见城市与水道的关系
从山上看尾道,最鲜明的是坡地建筑、铁路、海边街道与对岸岛屿之间的层次。 上山以后,不必立刻赶着下去,先在展望处看看水道上的船,再沿路径走向寺院,会更容易理解这座城市的地形。
缆车连接山麓与山顶,行程约3分钟。 具体开放时间与临时运行安排,可查[千光寺山缆车官网](https://mt-senkoji-rw.jp/fare/service/)。 若准备下山后再上山看夜景,应单独确认返程方式,不要沿用白天的缆车计划。
## 文学之小径与千光寺
文学之小径把与尾道有关的文学作品、碑刻和山间步行连接起来。 可以挑几处读懂的碑文停留,不必把它当成必须逐个完成的清单。 千光寺与公园展望处的位置也不同,沿路的岩石、台阶和建筑,都是这段步行的一部分。
想先了解各条路径的连接方式,可参考[观光协会的散步路线](https://www.ononavi.jp/fan/modelcourse/detail.html?detail%5Fid=2),再按当天的开放情况选择。
## 猫之细道:留意小路里的细节
猫之细道位于天宁寺三重塔附近,是一条约200米的窄巷。 观光协会的[介绍](https://www.ononavi.jp/sightseeing/showplace/detail.html?detail%5Fid=732)说明,园山春二自1998年起在这里放置“福石猫”,逐渐形成了这个爱称。 看这些藏在路边的石猫与装饰,比把目的只放在寻找活猫上,更能认识这条小路的特色。
巷道周边也有人居住、经营店铺。 沿开放道路走,遇到门前和私有空间就保留距离,让散步停留在不打扰日常生活的范围内。
## 午餐与海边的下午
回到商店街以后,可以选一碗尾道拉面,或找一家合适的餐厅坐下来休息。 尾道拉面的常见特点是酱油汤底与猪背脂,各店做法仍有不同,更多背景可见[观光协会的饮食问答](https://www.ononavi.jp/faq/question.html?qa%5Fid=3)。 若午餐时段人多,就按排队情况调整,给下午的散步留一点弹性。
本通商店街适合看店、喝咖啡和挑选土产,之后再走回海边,看渡船与对岸街景。 上半天从高处看地形,下半天在街道里慢慢走,这条路线的重点就有了自然的变化。 如果还想搭船去其他岛屿,最好另留半天或一天,避免把尾道市区和岛上景点都塞进同一个紧凑行程。
### 名古屋城之行
URL: https://www.sakashita.page/nagoya-castle/
Last updated: 2026-10-24T04:09:59.000Z
名古屋城的参观重点,可以放在三件事上:从外部看天守与金鯱,走进复原的本丸御殿,再观察城内保存的隅橹、石垣和庭园。
天守内部目前闭馆,本丸御殿则有独立的参观安排。 先分清这两处,就能避免把“不能登天守”理解成整座城都无法游览。
## 今天看到的是哪些年代的建筑

石垣上耸立的名古屋城天守
德川家康于1610年开始营建名古屋城,天守等主要建筑于1612年完成。 此后,它成为尾张德川家的居城。
1945年的空袭使天守、本丸御殿等建筑烧毁。 今天的大小天守于1959年以现代结构重建,而现存隅橹与城门等遗构,又保留了其他历史层次。

名古屋城天守与本丸御殿外观
目前天守闭馆,参观应以城内开放区域和本丸御殿为中心。 有关保存整备与将来的开放消息,直接查看官方公告即可。
[名古屋城:天守的现状www.nagoyajo.city.nagoya.jp](https://www.nagoyajo.city.nagoya.jp/guide/tenshu/)
## 屋顶上的金鯱

礼品店内的金鯱主题拍照模型
金鯱是名古屋城很有辨识度的标志。 鯱属于传统的想象动物,常见于城郭屋顶装饰,与防火祈愿等观念联系在一起。
天守屋顶的一对金鯱,经历过战火与重建。 从地面远看时,可以留意它们与屋檐的关系;在店铺或展示中看到模型,则更容易认识头部、背鳍和尾部的形态。
## 本丸御殿:看空间与装饰怎样配合

本丸御殿复原障壁画中的虎豹

御殿内的隔扇画与墙面装饰
本丸御殿原为藩主的居所与政务空间,后来也承担接待将军等用途。 战前留下的图纸、照片与保存下来的障壁画等资料,为复原提供了依据。
复原工程于2009年开始,2013年先开放玄关与表书院等部分,直到2018年才完成全面公开。 如今参观的御殿与其中大量装饰属于复原展示,保存的原作则有各自的展出安排。
### 上洛殿的细节

上洛殿的彩绘、天花与雕饰

上洛殿内层次丰富的装饰
上洛殿与1634年德川家光前往京都时的接待有关。 参观时,可以把天花、隔扇画和雕饰放在一起看,观察装饰如何随着房间用途与等级变化。
与其一眼扫过所有金色表面,不如在一两个细节上多停留片刻。 不同房间的题材、边框与空间高度,会让御殿的结构更容易被看见。
[名古屋城:本丸御殿www.nagoyajo.city.nagoya.jp](https://www.nagoyajo.city.nagoya.jp/guide/honmarugoten/)
## 隅橹、庭园与西之丸

石垣上保存的白色隅橹
隅橹是设置在城郭边角的防守建筑。 它们与天守在位置、规模和用途上各有区别,适合配合城内地图一起看。
二之丸庭园、石垣与西之丸御藏城宝馆,也能加入参观路线。 城宝馆的展览会轮换;隅橹是否特别公开,则要另看公告。 选择最感兴趣的两三处,可以比不断赶路看得更细。
## 金鯱横丁:在城外吃饭与逛店

金鯱横丁的木造店铺与拍照看板

城外通道与金鯱横丁的指示
金鯱横丁在名古屋城入口周边设有不同区域,集中了餐饮和伴手礼店。 可以把午餐安排在入城前,或参观后再慢慢挑店。

礼品店里的调味品与伴手礼

店铺门前的金鯱主题海报
季节菜单、商品与店铺营业时间都会变化。 照片里的展示可以作为当时的旅行记录,想找同样商品时,再查目前的店铺目录。
## 交通与参观安排
乘地铁名城线可到“名古屋城站”;它与新干线停靠的“名古屋站”是不同车站。 乘观光巴士的话,则查看名古屋城停靠点与当前班次。
入城、御殿入馆与闭园的截止时间并不相同。 出发前先看当天开放情况,想认真看御殿,就把它放在行程较早的位置。
[名古屋城:交通与开放安排www.nagoyajo.city.nagoya.jp](https://www.nagoyajo.city.nagoya.jp/access/)
### 东京晴空塔观光指南
URL: https://www.sakashita.page/tokyo-skytree-guide/
Last updated: 2026-10-24T03:59:59.000Z
东京晴空塔位于墨田区押上,高634米,兼有广播信号发射与观光功能。 到这里可以登塔看东京,也可以把水族馆、科学展示或博物馆安排进同一天。
第一次来时,先分清“晴空塔”“晴空塔城”和“东京晴空街道”这几个名称,会比较容易读懂楼层图。 晴空塔是高塔本身;晴空塔城是周边整片设施区域;东京晴空街道是其中的商业设施。
## 天望甲板与天望回廊怎样选
天望甲板分为350米、345米与340米三个高度,可以从不同楼层眺望城市。 340米层的玻璃地板也是其中一个看点,是否站上去拍照,按自己的舒适程度决定即可。
更高的天望回廊位于445米至450米一带,沿缓缓上升的回廊欣赏窗外景色。 如果已经确定要去两个区域,可以提前选择同时包含天望甲板与天望回廊的套票。
目前也有在天望甲板内购买回廊追加票的方式,但可能因人数限制而暂停销售。 票价会随日期、购买渠道和票种变化,预约时应直接查看所选日期。
能见度同样会影响登塔体验。 晴朗、起雾与下雨时看到的城市各不相同,若特别想看远山,可在购票前留意天气。
[东京晴空塔:门票与预约www.tokyo-skytree.jp](https://www.tokyo-skytree.jp/ticket/)
## 塔下还有哪些地方可以逛

东京晴空塔与旁边的东塔大楼

晴空塔城内的吉祥物与节日装饰
东京晴空街道集中了商店、餐厅与多种文化设施。 不同设施有独立的入口、售票方式和营业时间,登塔门票通常不包含它们的入场费用。
如果只安排半天,可以在登塔之后选一个最感兴趣的地方。 想慢慢逛店、吃饭,再加两三个展馆,则适合留出更宽裕的时间。
[东京晴空街道:楼层与设施目录www.tokyo-solamachi.jp](https://www.tokyo-solamachi.jp/)
## 千叶工业大学的科学展示

千叶工业大学展出的太空探测器模型
千叶工业大学的东京晴空塔城校区位于8层,对公众开放机器人与宇宙探索等研究主题的展示。 这里适合对科技感兴趣的读者,也能在登塔之外,换一种方式接近“天空”这个主题。

千叶工业大学东京晴空塔城校区的入口

展柜内不同形态的机器人模型
展项会随研究成果和企划调整。 照片记录了当时看到的模型,出发前可以先看校区目前的展示介绍与休馆日。
[千叶工业大学东京晴空塔城校区cit-skytree.jp](https://cit-skytree.jp/)
## 墨田水族馆

墨田水族馆入口
墨田水族馆位于晴空塔城西区的5层与6层。 馆内有企鹅、水母、花园鳗与金鱼等展示,也有以小笠原海域为主题的大型水槽。
这里的参观动线比较自由,可以在感兴趣的水槽前多停留一会儿。 如果打算观看喂食或其他讲解活动,先查当天安排,再决定参观顺序。
[墨田水族馆:展区与入馆信息www.sumida-aquarium.com](https://www.sumida-aquarium.com/)
## 天文馆“天空”
柯尼卡美能达天文馆“天空”位于7层,以穹顶影像、音乐与不同主题的作品呈现星空。 它按场次售票,适合提前选好节目,再安排前后的用餐和登塔时间。
普通座席与新月形座席的价格、可使用人数及年龄条件各有规定。 有些作品含香氛演出,是否提供外语支持也要逐场确认。 购票前先看作品说明,便于选择自己感兴趣、也能理解内容的一场。
[天文馆“天空”:节目与上映日程planetarium.konicaminolta.jp](https://planetarium.konicaminolta.jp/tenku/)
## 邮政博物馆

邮政博物馆入口处的展览海报
邮政博物馆位于东京晴空街道9层。 从邮票、书信、制服与邮政用品入手,可以看到邮件如何被收集、运输,再送到人们手里。

邮政博物馆内的接待区与晴空塔模型
常设展与企划展各有重点,实物展品和体验内容也会轮换。 喜欢邮票或近代生活史的话,可以先看展览主题,再决定停留多久。
[邮政博物馆:展览与开放日www.postalmuseum.jp](https://www.postalmuseum.jp/)
## 交通与一天的安排
东武晴空塔线的“东京晴空塔站”,以及半藏门线、都营浅草线等线路经过的“押上站”,都可以前往这一区域。 选择哪一站,取决于出发地与当天使用的线路。
- 🚃 抵达后:先确认登塔或展馆的预约时间,再按楼层图找入口。
- 🚶 白天:登塔与一个展馆搭配,中间留出午餐和休息时间。
- 🌃 傍晚:到塔外看看亮灯,再决定是否留在晴空街道用餐。
若为特定联名灯光而来,应另外查询亮灯日期与时段。 普通夜间亮灯和限定活动,日程可能并不相同。
[东京晴空塔:交通与当天公告www.tokyo-skytree.jp](https://www.tokyo-skytree.jp/)
### 指宿砂蒸温泉与“指宿玉手箱”列车
URL: https://www.sakashita.page/ibusuki-sand-baths-and-train/
Last updated: 2026-10-24T03:49:59.000Z
到指宿,可以把沿海铁路、砂蒸温泉与一晚海景住宿连在一起。 这次住在吟松,白天前往附近的砂蒸会馆“砂乐”,再沿海边散步。
砂蒸与泡在浴池里的温泉不同。 人们穿着专用浴衣躺在砂地上,由工作人员盖上受温泉热加温的砂,头部露在外面。 先了解利用流程,再把当天的交通和休息时间排进去,体验会比较从容。
## 乘“指宿玉手箱”列车前往
从鹿儿岛中央站,可以乘JR指宿枕崎线前往指宿。 如果想把乘车也作为旅行的一部分,可以选择特急“指宿玉手箱”。
列车黑白相间的外观与“浦岛太郎”的故事有关。 故事里,浦岛太郎从龙宫带回玉手箱,打开后白烟升起,自己也变成了白发老人。 列车用颜色和喷出的雾气呼应这个传说,让去海边的路上多了一点趣味。
这班特急全席指定,乘车前要安排座位。 运行日期、所需时间、票价与回程末班,应直接查看JR九州的当期信息。 也可以搭配普通列车往返,按自己的出发时间选择。
[JR九州:特急“指宿玉手箱”www.jrkyushu.co.jp](https://www.jrkyushu.co.jp/trains/ibusukinotamatebako/)
## 砂乐在哪里

砂蒸会馆砂乐台阶上的彩绘

海边的砂蒸温泉纪念拍照看板
砂乐位于指宿海边,从JR指宿站步行约20分钟,乘车约5分钟。 带着大件行李或天气炎热时,可以先比较公交、出租车与住宿设施提供的交通安排。

通向砂蒸会馆设施的自动扶梯
海滨的温泉热使砂地升温,设施则由工作人员管理体验区域和使用顺序。 能否在浪边利用,要看潮汐、天气等条件,由现场判断。
## 一次砂蒸怎样进行

砂蒸会馆砂乐的入口
抵达后,先在接待处确认利用条件,领取浴衣,再到更衣区域准备。 毛巾、储物柜与其他用品的费用,应看当前价目和现场说明。
换好浴衣后,按工作人员引导前往砂蒸区域。 盖砂的位置、厚度与停留时间都应听从指示。 如果觉得烫或身体不适,应及时告诉工作人员并结束体验,不必勉强待满某个时长。
结束后,按动线归还浴衣、冲洗身上的砂,再使用浴场与更衣设施。 整个过程还包括更衣、清洗和休息,安排交通时可按设施建议预留约一小时,并把排队时间另算进去。
目前砂乐不允许游客自行携带相机进入砂蒸等指定区域,拍照需查看官方提供的服务。 有关身体状况和利用限制,也应在体验前查看设施的说明。

会馆内餐饮空间的暖帘与菜单

砂乐的榻榻米休息区
照片记录了当时的休息与餐饮空间。 利用后留一点时间坐下来,再决定是否继续散步,不必马上赶下一段车。
[砂乐:利用方法、休馆日与常见问题www.ibusuki-saraku.jp](https://www.ibusuki-saraku.jp/ja)
## 沿着海边慢慢走

穿着浴衣在指宿海边散步

指宿海边步道与南国街景
海边散步可以安排在砂蒸之后,也可以留到第二天。 沿岸的天空、海面与街景变化很直接,走多远按天气和体力决定。
如果准备继续游览开闻岳或山川方向,最好单独查交通与所需时间。 它们适合放进更完整的指宿行程,而不必全挤进一次砂蒸的前后。
## 这次入住的吟松

吟松的日式客房
这次在吟松住了一晚。 日式客房、料理、海景与露天浴场,是这段住宿里想慢慢享受的部分。 想在这里住一晚,可以先看房型、浴场与用餐方式是否适合自己的行程。

吟松面向海景的休息大厅

从旅馆休息区眺望锦江湾
旅馆就在砂乐旁边,去海边散步也容易安排。 不同房型的浴室配置、露天浴场的开放时段,以及住宿方案包含的餐食各有差别,预订前可以逐项确认。
[吟松:客房、浴场与交通ginsyou.com](https://ginsyou.com/)
## 安排行程时的顺序
- 🚃 先确定鹿儿岛中央与指宿之间的往返列车,想乘观光列车就提前安排指定席。
- 🚶 抵达后,按行李和天气选择步行或乘车前往住宿设施、砂乐。
- 🛏️ 如果住一晚,把砂蒸与海边散步分开安排,给清洗、更衣和休息留下余量。
砂乐有检修休馆、平日中午暂停接待及繁忙时提前结束接待等安排。 出发前查好当天公告,再安排去程和回程。
### 日本女孩节:雏祭的由来与习俗
URL: https://www.sakashita.page/hinamatsuri-doll-festival/
Last updated: 2026-10-24T03:40:00.000Z
日本的雏祭在3月3日举行,也常被称为女孩节或桃之节句。 人们通过陈列雏人形、准备节日食物等方式,寄托对孩子健康成长的祝愿。 虽然不同国家也有以女孩为主题的纪念日,它们的起源和目的各不相同,不宜混为同一种节庆。
## 从纸偶到雏人形
雏祭与季节性的祓除习俗、玩偶游戏等传统逐渐结合。 把纸偶等放流的“流雏”习俗,在部分地方仍有保留;今天常见的雏人形陈列,则发展出精细的服饰、道具与摆放形式。 不同地区和家庭的庆祝方式并不完全相同。
## 陈列架上都有谁?

铺着红毯的多层雏人形陈列架
最上层的一对人形称为内里雏,造型让人联想到宫廷装束。 完整的多层陈列还会配上三人官女、五人乐师、随身、仕丁及嫁妆道具等。 也有只摆一对人形的简洁形式,不能把七层架当成每个家庭都必须遵循的标准。
看展时,不妨留意人物的表情、手中道具与衣服的层次。 同样是雏人形,年代、产地和制作手法不同,观感也会不同。
## 节日食物与悬挂布偶

以布制小偶串成的悬挂装饰
散寿司、蛤蜊清汤、菱饼和雏霰,是常见的雏祭食物与点心。 有些地区还会陈列成串的悬挂布偶,让一个个手缝的小物件承载祝愿。 这些习俗并不意味着每家都必须准备相同的菜单。
“雏祭后迟迟不收人形会耽误婚期”是流传的说法,不是可以验证的因果关系。 比起把节日变成对婚姻的催促,祝愿孩子平等、健康地成长,也许更值得保留下来。
### 伊豆城山赏樱之旅
URL: https://www.sakashita.page/izu-shiroyama-cherry-blossoms/
Last updated: 2026-10-24T03:29:59.000Z
这次赏花去的是伊豆之国市的狩野川神岛公园,也就是“川之驿伊豆城山”一带。 粉色花枝沿着河堤展开,绿色草坡与远处城山把景色分出层次。 比起先讲一遍整个日本的赏花史,我更想留下那天沿河走、在树下看鸟的片刻。
## 先分清花期预报与眼前花况

2024年樱花开花预测图,属于历史资料
当时参考的tenki.jp预测来自日本气象协会。 这里保存的是2024年的资料图,不能再当作今年的开花日历。 常见的染井吉野开花预测,也不能直接套到伊豆各处的早樱上。
准备再去时,先看[川之驿伊豆城山的官方信息](https://kanogawakamishima.com/),确认当地花况与活动。 赏花地点相似,不代表品种、进度与盛花期都会相同。
## 花枝之间的小鸟

停在粉红樱花枝头的绣眼鸟

在盛开的樱花间歇息的小鸟

伊豆城山附近盛开的粉红色樱花
树下最吸引我的,是不断在花枝间活动的小鸟。 白色眼圈在粉色花朵之间十分显眼,有时刚举起相机,它又跳到另一枝。 站在原处安静等一会儿,比追着鸟走更容易看清它们的动作。
拍到鸟与花同框,是这次出门的小收获。 不需要为了靠近而摇动树枝或摆放食物,让它们按自己的节奏活动就好。
## 沿河散步与当时的小摊
河堤适合慢慢走,喜欢的视角可以回头再看。 若从车站步行进入公园,应把过桥与最后一段道路也算进路线,不只按地图上的直线距离估时。

赏樱会场旁的黄色餐车

赏樱期间摆设的户外小摊
这次会场旁有餐车和户外小摊。 它们是当时赏花活动的一部分,不代表每个周末、每个季节都在同一位置营业。 再访时想安排用餐,可以先查活动通知,也为周边其他选择留一点余地。

城山樱花会场的钟与照片展板
## 怎样找到这片河岸
目的地地址是静冈县伊豆之国市神岛141-4。 乘伊豆箱根铁道时,可按自己的出发方向比较田京、大仁等站的步行或接驳方案,再核对公园的实际入口。 这里的赏花散步与登城山、攀岩是不同活动,不要因为名称相同就把路线混在一起。
花开时去看花,没有遇上盛花,也可以看河流、草坡与山的关系。 那天的照片里,最想留下的仍是花枝间短短一停的小鸟。
### 南伊豆的河津樱与油菜花
URL: https://www.sakashita.page/minami-izu-cherry-blossoms/
Last updated: 2026-10-24T03:19:59.000Z
2024年早春,我们去了南伊豆町下贺茂温泉一带。 青野川边是粉色河津樱,走到日野又能看到黄色菜花,白天与夜间的光线让同一段散步有了不同的表情。 这篇保存那次旅行的照片,花况与活动安排都属于当时的记录。
## 从伊豆急下田站接上巴士
从东京方向乘铁路到伊豆急下田站后,再换乘往下贺茂方向的东海巴士。 去程之外也先看返程班次,尤其要看夜樱时,不能默认白天的巴士一直运行到活动结束。

旅行时参考的东海巴士通票海报,票价与范围应重新确认
当时参考过巴士通票,但是否值得买,仍取决于两天内实际会走的路线。 石廊崎、下田港游船与下贺茂赏花是不同地点,不能因为出现在同一份宣传里,就把它们全排成一段没有换乘的行程。
## 青野川:在河岸上慢慢看花

青野川岸边盛开的河津樱

在粉红色樱花枝头觅食的绣眼鸟

青野川旁的樱花、油菜花与散步道
沿青野川走,可以看见樱花与菜花在河岸间交错。 树下的小鸟又让视线从远处的花景回到一根花枝上,停一会儿,便能看见它在花间来回移动。 这段散步适合留出弹性,不用为每一个桥头设定固定停留时间。
## 河津樱的名字,不等于同一个樱花祭

南伊豆河堤旁的樱花小径
河津樱的发现与命名背景在河津町,相关原木也在那里。 南伊豆青野川种植的是这一类早樱,但南伊豆的“南之樱和菜花祭”与河津町的“河津樱花祭”是两个活动。

南伊豆樱花与菜花祭的散步导览图

赏樱步道旁的双人地藏石像
当地活动与开花消息,可看[南伊豆町观光协会](https://www.minami-izu.jp/)。 想在两处赏花地之间选择,也可以先看[河津町与南伊豆的路线比较](https://www.sakashita.page/kawazu-cherry-blossoms/),把最后一段交通分清再决定。
## 夜樱与竹灯

南伊豆夜樱会场亮灯的桥梁

光影竹灯和夜樱照明
天暗以后,桥梁与竹灯亮起,白天看过的河岸有了另一种深浅。 夜樱不只是把白天的照片换成暗背景,花枝、水面与灯之间的距离也值得慢慢观察。
点灯是特定会期的安排。 照片中的时间与装饰不能保证以后每年都一样,准备看夜景时应另外确认当年的通知与返程。
## 日野菜花田

日野菜花田中的花框拍照装置

夕阳下金黄色的日野菜花田
沿河散步以后,我们继续去了日野的菜花田。 黄色花海与刚才的樱花形成鲜明变化,夕阳落下来时,田间装置与远处景色也显得柔和。 赏花要沿开放的小径走,不为了拍照进入种植区域。

日野菜花田边的巴士站
巴士站就在旅行路线的一部分里,离开前再看一眼返程信息,便能安心给最后几张照片留一点时间。 这次印象最深的,是粉色与黄色之间的切换,以及花枝上那些不断忙碌的小鸟。
### 两处河津樱赏花地推荐
URL: https://www.sakashita.page/kawazu-cherry-blossoms/
Last updated: 2026-10-24T03:09:59.000Z
河津樱是伊豆早春旅行里很醒目的风景。 这里介绍的两处赏花地,分别位于河津町与南伊豆町:名字都与河津樱有关,却不在同一座车站旁,也不是同一个樱花祭。 选行程时,先分清河流、车站与最后一段交通,会比只搜索“伊豆樱花”更容易安排。
## 河津町:从河津站走向河岸
河津町的代表景观是河津川沿岸的樱花。 从河津站一带进入步行路线,可以沿河看花,再按体力决定走多远。 河津七滝属于另一处游览区域,需要另外安排交通,不是沿河散步时顺便就能走到的一段。
从东京方向出发,可以经热海、伊东进入伊豆急行线,也可以比较直通列车。 赏花时段人多,去程之外也要先想好返程,不要等到傍晚才开始找合适的班次。
河津樱通常在2月前后进入观赏季,但每年的进度不同。 河津町观光协会的[花期说明](https://www.kawazu-onsen.com/sakura/?vm=r)把开花过程与盛花期分开介绍,出发前应继续看当年的实况。 例如2026年的河津樱花祭安排在2月7日至3月8日,不能把旧文章里的日期当成每年固定的会期。
[河津町观光协会的活动通知www.kawazu-onsen.com](https://www.kawazu-onsen.com/news/502)
## 南伊豆町:青野川与下贺茂温泉
南伊豆的赏花区域位于下贺茂温泉一带,重点是青野川沿岸的早樱与周边菜花景观。 沿河散步、找一处视野开阔的桥边停留,再把温泉或午餐接进来,就可以形成节奏舒缓的一天。 樱花、菜花和夜间点灯各有观赏条件,不保证在整个活动期间始终处于同样状态。
乘铁路时,先到伊豆急下田站,再换往下贺茂方向的巴士。 这条路线与河津町的赏花交通不同,住宿在下田或南伊豆时尤其值得分开比较。 准备自驾的话,也应先看当年的停车与交通安排。
“南之樱和菜花祭”的会期每年需重新确认。 准备出发时,请以[南伊豆町观光协会](https://www.minami-izu.jp/)发布的当年活动、开花和交通消息为准。
## 怎样选择两处赏花地
如果希望下车后尽快进入沿河步行,河津站一带比较容易组织行程。 如果本来就计划住在下田、下贺茂或南伊豆,可以把青野川安排在住宿路线附近。 两处都想看时,先把铁路与巴士换乘排出来,再判断是否值得同一天往返,不必为了“集齐”而把赏花变成赶路。
拍照以开放的步行区域为主,不攀折花枝,也不要为了取景挡住狭窄通道。 花况适合、天气舒适、自己有时间慢慢走,这些条件比刚好赶上活动的第一天更重要。
### 樱岛散步:熔岩海岸、火山与黑神鸟居
URL: https://www.sakashita.page/sakurajima-half-day-trip/
Last updated: 2026-10-24T03:00:00.000Z
这次樱岛旅行从早上的渡轮开始,到下午两点左右返回鹿儿岛市区。 我先走西侧海岸,再另搭巴士去黑神鸟居,最后回到港边买土产。 如果只有半天,最好只选西侧几个地点;黑神位于另一侧,需要单独核对往返交通。

鹿儿岛美丽的樱岛
樱岛仍是一座活跃火山,1914年大正喷发让它与大隅半岛陆地相连,并不是它“最近一次喷发”的年份。 火山活动、警报与限制区域,可从[日本气象厅的樱岛页面](https://www.data.jma.go.jp/vois/data/fukuoka/506%5FSakurajima/506%5Findex.html)确认。 旅行按开放道路走,不把近看火山理解成可以接近火口。
## 港口与西侧观光巴士

樱岛环岛观光巴士
渡轮连接鹿儿岛市区与樱岛港,抵达以后还要继续步行或换乘。 西侧周游巴士与前往黑神等地的线路范围不同,不能把它当成覆盖整座樱岛的环岛车。 [鹿儿岛县的樱岛介绍](https://www.kagoshima-kankou.com/feature/sakurajima/top)提供交通与各区域的入口,行前再按运营方时刻表安排。
## 熔岩海岸与足汤

樱岛熔岩海岸旁的长条天然足汤
我到港时,游客中心还没开始营业,于是先往熔岩海岸走。 足汤与之后的海岸散步,让这一天从港口的移动转换成了停留。 是否可以使用足汤、当日开放到几点,仍要看现场安排。

樱岛熔岩海岸
熔岩的表面与其间生长的植物,适合近距离慢慢看。 熔岩表面与植物的变化,留下了火山喷发后自然演变的痕迹。
## 乌岛展望所与赤水广场

乌岛展望所的石砌导览台
乌岛原是海中的小岛,后来被大正喷发的熔岩覆盖。 [官方景点记录](https://www.kagoshima-kankou.com/guide/10566)把它与今天的展望位置联系起来,读过以后再看周围地形,会比只记一组高度与周长更直观。

樱岛观光巴士的站牌

纪念长渕刚樱岛演唱会的雕塑《呐喊的肖像》
赤水展望广场的雕塑,与长渕刚2004年在樱岛举行的通宵演唱会有关。 一座表现呐喊的石像放在火山与海景之间,让这段路又多了音乐记忆的线索。 歌手的履历、翻唱关系与“婚礼必唱”等说法,并不是理解眼前雕塑的必要条件。
## 物产馆一带

樱岛物产馆外的巨大樱岛萝卜模型
大萝卜模型很醒目,让人记住了樱岛的地方物产。 模型与吉尼斯纪录不是同一件实物,参观时看现场说明即可,不需要拿一个旧重量当成今天每颗萝卜的代表。
之后我回到港口,重新安排去黑神的巴士。 这段往返需要看实际班次,不建议在临时起意、没有查好回程时直接出发。
## 黑神淹没鸟居

黑神淹没鸟居
黑神鸟居在1914年喷发中被火山灰、轻石等掩埋,只剩上方一部分露在地面。 这与西岸乌岛被熔岩覆盖,是不同地点的遗迹,不能用同一种“熔岩淹没”解释所有景观。 [鹿儿岛县的说明](https://www.kagoshima-kankou.com/guide/10531)介绍了保留鸟居、向后人传达喷发影响的背景。
照片里很低的横木,原本属于一座约3米高的鸟居。 站在旁边看它与地面的关系,比只看图片更容易感到地形改变的尺度。
## 回到游客中心与港边

樱岛游客中心的正面入口

游客中心内介绍火山的影像放映区
回来以后,我再到游客中心看火山历史与地质介绍。 有了上午看到的熔岩、展望所与鸟居,这些说明也更容易与实际地点相接。 影像、租车和商店的使用安排则以当日公告为准。

樱岛火之岛惠馆的农产与特产商店
最后在火之岛惠馆挑了土产,再搭船回鹿儿岛。 这段购物不需要另排很长时间,却让旅行从地形回到当地的日常物产。

在樱岛购买的鹿儿岛烧酒

樱岛主题的小点心与咖啡伴手礼

樱岛当地出产的山茶油
想走得轻松,就把西侧散步作为一段完整路线。 有更多时间,再把黑神等地点接进来;这一天留下的重点,是火山怎样改变海岸,以及人们怎样继续在这里生活。
### 东京晴空塔与《原神》的特别照明
URL: https://www.sakashita.page/tokyo-skytree-genshin-lighting/
Last updated: 2026-10-24T02:49:59.000Z
2024年2月17日,我去了东京晴空塔,想拍下《原神》联动活动的特别照明。 平时喜欢的游戏角色化成塔身上的颜色,远远看去,熟悉的夜景也有了另一种表情。 这里保存的是那晚的照片,不是目前仍在举行的活动预告。
## 那次联动活动
《原神》与东京晴空塔的联动于2024年1月17日至3月6日举行。 官方活动名称中的“雷灵”并不是“雷神”,所指也不能直接换成某位角色。 [晴空塔当时的公告](https://www.tokyo-skytree.jp/press/post/593/)介绍了角色装饰、拍照区域、合作商品、咖啡菜单与特别影像等内容。
展望设施里的活动与户外塔身照明是两种观看体验。 前者需要按活动当时的入场安排参观,后者则让人在塔外也能看到颜色的变化。 特别照明以八位角色为灵感,在指定日期点亮,并不是整个活动期间每晚都有同样安排。
## 把颜色留在照片里

《原神》联动灯光中的金黄色晴空塔

晴空塔《原神》联动的绿色与橙红色照明

晴空塔《原神》联动的青色与红色照明
同一座塔,换上不同配色以后,轮廓的重心也像跟着移动。 有时是暖色先映入眼帘,有时则是冷色把塔身与夜空分开。 照片把不断变化的灯光停在了某一刻,看起来便像一组彼此呼应的夜景。

晴空塔《原神》联动的粉红与绿色照明

晴空塔《原神》联动的紫色与粉红色照明

晴空塔《原神》联动的浅蓝与白色照明
那晚拍摄时,我更在意把每种配色记录下来,没有把这次出门安排成一份完整的展望台游记。 因此,这组照片适合当作一次特别照明的记录,而不是对联动展区的全面介绍。

晴空塔《原神》联动的深蓝与红色照明

夜空中点亮蓝白色灯光的晴空塔
这次联动已经结束,文中的日期与活动内容都属于2024年。 以后想去看晴空塔的灯光,可以重新查阅[官方照明日程](https://www.tokyo-skytree.jp/enjoy/lighting/),再选择适合自己的拍摄时间。
### 鹿儿岛市内的观光景点
URL: https://www.sakashita.page/kagoshima-city-guide/
Last updated: 2026-10-24T02:39:59.000Z
这次抵达鹿儿岛机场时已经是中午,办好入住后,我才开始市内观光。 先坐观光巴士绕一圈,从车窗认识城市,再回到感兴趣的地点慢慢走,是这段旅程的开头。
我住在城山饭店鹿儿岛,看重的是海与樱岛方向的景色,也利用了饭店往返市区的接驳。 当时的住宿价格与接驳安排,属于那次旅行的记录;准备预订时,可以按目前的房型和时刻重新比较。
## 用市电与观光巴士串起路线

鹿儿岛街头的路面电车
市电适合在中央站、天文馆与市役所周边移动,观光巴士则把几个主要景点串在一起。 鹿儿岛站与鹿儿岛中央站是不同的车站,新干线抵达的是鹿儿岛中央站,查接驳时要认准名称。

车站通道的拱形顶棚与阶梯
如果当天同时去市区与樱岛,可以比较“CUTE”共通利用券。 它目前涵盖指定的市营公交、市电、观光循环巴士和樱岛渡轮,分一日券与二日券。
民营公交、其他渡轮或机场交通,各有独立的使用条件。 先确认实际要坐的线路,再比较票价与有效日期,会比只看“无限次”几个字更容易选对。
[鹿儿岛市:CUTE票券与利用范围www.city.kagoshima.lg.jp](https://www.city.kagoshima.lg.jp/sakurajima-ferry/unchin/cute.html)
## 鹿儿岛城与御楼门

鹿儿岛城御楼门的正面与石垣
鹿儿岛城也称鹤丸城,是岛津家的居城。 后来成为岛津家第18代当主、萨摩藩首任藩主的家久,在1601年前后开始营建这座城。
御楼门于1873年火灾中烧毁,2020年复原。 从门前看屋顶、白墙、木构与石垣,能直接感受到入口的尺度。 绕到侧面,窗格与屋檐又呈现另一种轮廓。

城址内的天璋院笃姬像
城址、纪念像与周边文化设施适合连着看。 是否能穿过门、进入某一片区域,应配合黎明馆及现场的开放安排。
[鹿儿岛市:鹤丸城御楼门www.kagoshima-yokanavi.jp](https://www.kagoshima-yokanavi.jp/spot/20047)
## 黎明馆与市立美术馆

鹿儿岛县历史与美术中心黎明馆
这次到访恰逢星期一,黎明馆与市立美术馆都没有开门。 我便在周围散步,看看外观与街景,也给下一次留下了入馆的理由。

鹿儿岛市立美术馆入口与建筑
想看历史展示或美术展的话,应先确认两个馆各自的休馆日。 馆藏介绍与当期展览的内容并不相同,可以按兴趣选择一处多留些时间。

市立美术馆附近水道里的锦鲤
让我意外停下来的,是路边水道里的锦鲤。 绯红与橙色在水中游动,原本只是景点之间的一段路,也因此有了值得记住的细节。
## 西乡隆盛像与中央公民馆

城山前身着军装的西乡隆盛铜像
城山前的西乡隆盛像身着军装,与东京上野公园那座常见的形象不同。 了解明治维新与西南战争时,可以把这两种纪念形象放在一起想,看看不同地方怎样记忆同一人物。

西乡隆盛摄影广场与城山方向的街景

鹿儿岛市中央公民馆
附近的中央公民馆又呈现了另一种近代建筑风格。 它仍是市民使用的公共设施,参观时按开放空间行走,留意当天是否有会议或活动。
## 这一天的最后一站:鹿儿岛水族馆

鹿儿岛水族馆入口,照片保留拍摄时的现场说明
下午,我乘观光巴士到鹿儿岛水族馆。 这里以锦江湾、黑潮等海洋环境为线索,设有大型水槽、水母与海豚等展示。 当时看了海豚的讲解活动,也听工作人员介绍它们的身体与能力。
不同展区、喂食和讲解各有时间安排,动物展示也可能调整。 如果只剩半天,不必把每层楼都排成必须完成的清单,可以先看自己最感兴趣的内容。
馆外另有海豚水路,能够在开放区域免费观看,但是否能看到海豚,要看当天的活动、天气与动物状况。 想为这个安排时间,最好抵达后先问工作人员。
[鹿儿岛水族馆:展区与当日活动ioworld.jp](https://ioworld.jp/)
## 一天与两天的取舍
- 🚌 时间有限:从市区交通入手,选择御楼门、周边街区与一个文化设施。
- 🚶 慢慢散步:把纪念像、公民馆与路边水道串起来,在天文馆一带休息或用餐。
- ⛴️ 另留半天:若还想去樱岛,先看渡轮与岛上交通,再决定是否与市区放在同一天。
这次初到鹿儿岛,我最喜欢的是能按自己的节奏停下来。 城门、锦鲤与海边的水族馆,各自留下的印象,比一口气赶完所有地点更清楚。
### 知览一日游:鹿儿岛之行第三日
URL: https://www.sakashita.page/chiran-day-trip/
Last updated: 2026-10-24T02:29:59.000Z
知览适合用步行认识。 沿着石垣与修剪整齐的树篱走进武家屋敷街,再进入几座庭园,街道的尺度与院中的山石便会连接起来。 从鹿儿岛市区往返,可以把一天的时间留给庭园与旧街区。
## 从鹿儿岛市区前往知览
从鹿儿岛中央站或天文馆一带出发,可以查往知览方向的巴士,在武家屋敷入口附近下车。 路线、发车站与班次应按当天时刻表确认,官方入口见[鹿儿岛县的知览武家屋敷介绍](https://www.kagoshima-kankou.com/guide/11053)。
如果从Marine Port鹿儿岛出发,港口到市区巴士站的这一段还要单独安排。 港口出发与市中心出发的路线不同,需分别确认接驳与换乘。 邮轮旅客还应先确认船方要求的返船时间,再决定能否完成往返。
## 在武家屋敷街看什么
知览的旧街区保存了武士聚落的道路与宅地格局,沿路的石墙、树篱和门,是庭园以外也值得看的部分。 这里仍与日常生活相连,不是所有房屋都作为博物馆开放。 参观时按开放入口走,不因看见一扇门就推定可以进去。
## 七座庭园,各有不同的构图
七座公开庭园被指定为日本国家名胜。 其中六座以枯山水为主,森氏庭园则有实际的池水,可以在参观时留意两种庭园的差别。 庭园与周围山景的关系、石头的摆放和植物修剪方式,都值得分别比较。
[奈良文化财研究所的庭园记录](https://www.nabunken.go.jp/org/bunka/jgd/pages/GardensAtChiranfumoto.html)介绍了这些庭园的年代与设计特征。 带着一点背景去看,会比只记住“江户时代武士住在这里”更具体。
## 一天怎样走得从容
1. 🚌 按去程与返程班次安排抵达,不把最后一班当成唯一退路。
2. 🚶 先沿武家屋敷街步行,认识街道与庭园入口的位置。
3. 进入开放庭园,比较水、石、树篱与借景怎样组织在一起。
4. 在周边用餐或休息,再按时间决定是否增加其他景点。
庭园参观不必追求每处停留完全一样长。 喜欢的地方多看一会儿,走完以后回到街道,再看院墙与远山,整段路线就会有内外两种视角。
## 出发前再确认
七座庭园的票券、开放时间与临时通知,见[庭园运营方官网](https://chiran-bukeyashiki.com/)。 若还准备去知览和平会馆,需另算移动和看展时间,不能把两个主题差异很大的参观压在返程前的空隙里。
知览这一天的价值,在于愿意把脚步放慢。 从一条路到一座院子,看见石墙和植物如何划出生活空间,已经足以形成一段完整的旅行记忆。
### 日本的花街:认识艺妓与舞妓
URL: https://www.sakashita.page/geisha-and-maiko/
Last updated: 2026-10-24T02:19:59.000Z
2024年2月初,经日本朋友介绍,我第一次在料亭参加有艺妓与舞妓表演的宴席。 到场之前,我对“花街”还有不少模糊的想象。 真正留下印象的,却是歌舞、料理与席间游戏怎样配合在一起,让一晚的时间过得轻松而有趣。
店家不允许把席间照片公开,因此这里保留街区相关的照片,不展示那次宴席的影像。 这也是理解这类场合的起点:先接受主办方与表演者的规则,再谈怎样观看和记录。

京都花见小路一带的传统街景
## 艺妓与舞妓,不是两个年龄标签
在京都,艺妓是从事舞蹈、音乐等艺能表演的专业人员,舞妓则处于学习与修行阶段。 学习、见习与独立活动的安排,也因地区与个人情况而不同。
基本背景可参考京都传统技艺振兴财团的[舞妓介绍](https://www.ookinizaidan.com/kagai/maiko/)与[艺妓介绍](https://www.ookinizaidan.com/kagai/geiko/)。 她们的训练与工作,值得从技艺本身来认识,而不是先用猜测替整个行业下判断。

京都街头的和服与发饰,服饰细节不能单独证明职业阶段
## 服饰可以观察,身份不必急着判断

京都巷子里并行的两位和服着装者
舞妓的长垂带、发饰与和服样式常受注意,艺妓的装束也有自己的特点。 不过,季节、修行阶段、场合与扮装体验都会影响外观。 只凭嘴唇涂法或领子颜色,就宣称能一眼判定所有人的身份,并不可靠。
在街上遇见身着华丽和服的人,也不意味着对方正在为游客提供拍摄服务。 不追赶、不阻拦、不触碰衣物,看到禁止拍摄或私人道路的标志就遵守,比拍到一张近照更重要。
## 宴席上的音乐与舞蹈
表演中可以区分负责音乐的“地方”与负责舞蹈的“立方”等角色。 三味线、歌声与舞蹈相互配合,让动作与节奏连接起来。 观看时不必只盯着服装,也可以听一听伴奏怎样托住每一次转身与停顿。
席间游戏则把表演与交流拉近。 例如以老虎、老婆婆与武将构成互相克制关系的游戏,动作与节奏让规则变得直观。 是否饮酒、怎样参与,应按自己的情况与当场安排来,不是输了就必须喝酒的强制程序。
## 预约与礼貌,先问清楚

花街茶屋门前张贴的演出海报
一些传统茶屋需要介绍,其他公开演出或体验项目则有不同预约方式。 不能把“一见客不接待”当成京都所有花街文化活动的统一门槛。
费用、摄影与额外谢礼都应先向接待方确认。 表达谢意时,遵守接待方的方式,尊重表演者的身体与衣物。 席间的分寸感,比一套传闻中的动作更重要。
## 也可以从公开舞台认识花街

花街传统木造房屋的朱红色格栅
京都有祇园甲部、宫川町、先斗町、上七轩与祇园东五个花街,各自有演出与年中活动。 想先看艺能,可以从[五花街的官方演出信息](https://www.ookinizaidan.com/)选择,不一定要从私人宴席开始。
选演出时,查看当年的主办方通知,再分别确认公开舞台与私人宴席的预约条件,会更容易找到适合自己的入口。
### 日本的寺庙与神社:参访前的基本知识
URL: https://www.sakashita.page/japanese-temples-and-shrines/
Last updated: 2026-10-24T02:09:59.000Z
去日本旅行,寺院与神社常常就在同一条散步路线里。 它们在信仰、建筑与仪式上各有特点,也曾长期相互影响。 参访前知道几个区别,会比背下一套“所有地方通用”的动作更有帮助。
## 寺院:合掌,也留意各自的规矩

京都清水寺
佛教寺院一般供奉佛像,境内可能有本堂、佛塔、墓地与其他堂宇。 普通参拜时常见合掌、礼拜,与神社拍手的方式不同。 不过,进入哪一座堂、是否需要脱鞋、能否拍摄佛像,以及如何上香,都应先看寺院说明。
参拜不等于必须购买护符、写御朱印或参加收费祈愿。 安静参观开放区域、尊重正在举行的法要,也是一种合适的到访方式。
## 神社:鸟居与参道

长崎市风头神社
神社是祭祀神道神明的场所,鸟居常作为进入神域的标志。 它与中文里一般说的牌坊并非完全相同的建筑概念。 入口附近有时能看到狛犬,也有神社使用其他与信仰有关的动物形象,不能预设每处都长得一样。
## 净手与礼拜

神社参拜前净手的手水舍
手水舍用于参拜前净手、漱口,使用时不把水舀进嘴里,也不让口直接接触共用水勺。 有些地方采用流水或其他安排,按现场说明使用即可。
神社常见的基本形式是两次礼拜、两次拍手、最后一次礼拜,但也有不同做法。 [神社本厅的参拜说明](https://www.jinjahoncho.or.jp/omairi/sanpai/)明确要求尊重各神社自己的礼仪。 若正在举行仪式,先等候或询问,不必为了完成一套动作而打断他人。
## 神职与巫女

神社中身着祭仪服装的神职人员

神社境内身穿白衣红裙的巫女
神职人员负责祭仪等事务,巫女也会参与神乐、仪式辅助与授与所的工作。 他们的职责与服装随场合而异,不宜只凭一顶帽子、一件衣服就推断职务高低。
看见仪式时,可以在允许的位置观看。 想拍近照或人物特写,应先确认规定,不把正在工作的人当成随时可以摆拍的对象。
## 绘马、护符与签

千光寺的祈愿牌
绘马是写下愿望、奉纳于指定位置的木牌,在寺院与神社都能见到。 护符承载祈愿,抽签则是一种占问与自我提醒的习俗。 这些都属于信仰与文化实践,不意味着购买以后一定获得相应结果。
写愿望时,也可以想一想是否要留下完整姓名或住址。 木牌常处于公开可见的地方,保留自己的隐私与尊重他人的祈愿同样重要。
## 御朱印与御朱印帐

宫崎县鹈户神社御朱印
御朱印通常结合朱印、文字与日期,作为参拜的记录。 它与奉纳经典的历史有关,如今的办理方式则因寺社而异。 更多背景可见[神社本厅的御朱印介绍](https://www.jinjahoncho.or.jp/omairi/goshuin/)。
今天有的地方现场书写,有的提供预先写好的纸张,也有不办理的地方。 先完成参拜,再按指定地点、时间与费用安排询问,比把它当成必须盖到的观光印章更合适。
### 草津温泉的地标“温泉之门”
URL: https://www.sakashita.page/kusatsu-onsen-gate/
Last updated: 2026-10-24T01:59:59.000Z
2023年10月,草津温泉入口一带建成了“温泉之门”。 它既是一处迎接旅人的地标,也与国道292号及町道交会处的立体化工程有关。 木材装饰让道路设施多了一层温暖的表情,夜里点灯后,水汽与灯光又呈现不同的气氛。
## 门下的足汤
温泉之门旁整备了以汤畑为意象的足汤空间。 三条导水槽把温泉引向足汤池,并非“三条长浴槽”;坐下来时,可以留意水流经过木槽的样子与声音。 相比匆匆拍一张地标照,这里也适合作为步行途中的短暂停留。
足汤不是全身入浴设施,使用时带一条擦脚的小毛巾会更方便。 座位与周围通道是共用空间,不要把行李摊在他人经过的位置。
## 把这里作为散步起点
附近设有停车场,自驾时可以先确认停放条件,再步行进入温泉街。 停车位有限,不能把“有大型停车场”理解成任何时间都能轻松停到车。 若从巴士总站出发,也应先看温泉门、汤畑和住宿处之间的距离,选择适合自己的步行路线。
当前设施与停车信息可参阅[草津温泉观光协会的温泉门介绍](https://www.kusatsu-onsen.ne.jp/kankou/1257.php)。
### 相扑的历史与观赛入门
URL: https://www.sakashita.page/sumo-guide/
Last updated: 2026-10-24T01:49:59.000Z
第一次看相扑,未必需要先记住所有招式。 先认识土俵、力士等级和比赛前的动作,就能看出一场交锋里,力量、重心与时机怎样一起发挥作用。 比赛可能很快结束,入场仪式、准备和等待却也构成了现场体验。
## 从历史认识大相扑

摆出四股姿势的相扑力士插图
相扑既是竞技,也与祭祀和表演的历史相连。 《古事记》《日本书纪》中的力比传说,不能直接当成现代大相扑的比赛记录。 宫廷仪式、武士的训练和后来的商业兴行,属于不同阶段。 江户时代,职业力士与定期兴行逐渐发展,形成了今天大相扑的重要基础。 [日本相扑协会的历史介绍](https://www.sumo.or.jp/IrohaKnowledge/sumo%5Fhistory/)梳理了这条线索。
协会组织的成立也要与运动的起源分开。 根据[协会沿革](https://www.sumo.or.jp/IrohaKyokai/history/),财团法人大日本相扑协会成立于1925年,东京与大阪的两个协会在1927年合并。

相扑力士的手印与纪念签名
## 看懂一场胜负
圆形比赛区域叫土俵,两位力士腰间系着比赛用的腰带。 最基本的胜负判断是:先出界,或脚底以外的身体部位先触地的一方落败。 因此,一只手撑地也可能已经决定胜负,并不需要把对手摔到躺下。 [日本武道馆的相扑教材](https://www.nipponbudokan.or.jp/pdf/shidosho/sumo/Sumo.pdf)用这一原则介绍基本规则。
胜利常称“白星”,失败称“黑星”。 “金星”则有特定含义:在正式本场所比赛中,前头力士击败横纲;优胜决定赛的胜利不算在内。 它不是对任何以弱胜强比赛的泛称。
土俵上的行司负责主持取组,并用军配示意胜方。 场边另有审判委员观察比赛,出现争议时会进行称为“物言”的合议。 不要把行司、场边审判和负责呼唤力士登场的呼出混为一职。
## 番付与关取是什么

以相扑字书写的番付等级表
番付表用独特的相扑字书写,记载力士的地位、姓名和出身地等信息。 字号的大小与所在位置,也能帮助读者辨认等级。 刚开始阅读时,不必逐字辨认,先找到自己关注的力士,再看他属于哪一层即可。
从低到高,主要级别为序之口、序二段、三段目、幕下、十两与幕内。 幕内又有前头、小结、关胁、大关和横纲等地位。 十两与幕内力士合称“关取”,它不是十两之上另设的一个级别。 [日本相扑协会的说明](https://sumo.or.jp/Entertainment/quiz/1233)也专门解释了这个区别。

展示力士排名的相扑番付表
力士的地位与成绩有关,但不同地位的升降规则并不完全相同。 观看一整届比赛时,除了当天胜负,也可以留意同一位力士的累计战绩,以及他面对的对手。 这样再回头看番付,就不只是一张陌生名字组成的表格。
## 在哪里看正式比赛

大相扑秋场所的主题插画
大相扑通常每年有六次本场所:一月、五月和九月在东京,三月在大阪,七月在名古屋,十一月在福冈。 具体日期、会场和售票日,可查[协会的年度日程表](https://www.sumo.or.jp/Admission/schedule/)。 巡业则是另一类活动,不能直接当成同样计入本场所战绩的比赛。
不同会场的座位设置并不完全一样。 初次观赛,可以先比较椅子席与枡席:前者通常更容易安排坐姿,后者则要认真看定员、空间与整组售票的规则。 购买多人枡席时,还要确认标价是整组还是每人,避免只看单个数字来比较。
靠近土俵的溜席限制较多,也存在力士跌出土俵时波及观众的可能。 [协会的观赛须知](https://www.sumo.or.jp/IrohaOther/chuui)列有年龄条件、禁止饮食与未经许可拍摄等要求。 距离近不等于适合所有人,应结合自己的身体状况和观赛方式选择。
票价、当日销售与可售席种都以当届公告为准,不要预设到现场一定能买到某种自由席。 选好比赛日以后,再接着安排交通和用餐,会比先订好整天行程、最后才找票更稳妥。
## 晨练见学是另一种体验

相扑训练动作的示意插图
力士所属的训练生活组织称为相扑部屋。 晨练中能看到抬腿落脚的四股等基本动作,也能感受到反复训练的节奏。 但部屋首先是训练与生活的场所,并非每一家都对游客开放。
荒汐部屋公布了[晨练见学安排](https://arashio.net/tour.html):普通公众从室外隔着玻璃观看,是否有训练应先查页面上的日历。 日程可能临时改变,官网也明确请访客不要为确认练习日期而致电。 观看时保持安静、不使用闪光灯、不在附近住户的台阶上坐下,也不要把室外观看理解成可以进入部屋。
正式比赛适合感受一整天的现场气氛,晨练则更接近持续而重复的训练过程。 按自己的兴趣选择其中一种,认真看几场交锋或一段练习,比急着把两种体验都排进同一天更容易留下印象。
### 东京皇居之行
URL: https://www.sakashita.page/tokyo-imperial-palace/
Last updated: 2026-10-24T01:40:00.000Z
从东京站丸之内一侧向西走,城市街道逐渐让位给开阔的广场、护城河与石垣。 今天的皇居位于旧江户城一带,参观时,既能看见城郭留下的痕迹,也要分清哪些空间可以自由散步,哪些需要另外办理参观。
## 皇居外苑、东御苑与皇居参观
这三个名称并不是同一条观光路线。 皇居外苑适合看广场、护城河与正门方向的景观;东御苑是旧江户城本丸、二之丸、三之丸的一部分,作为皇居附属庭园对外开放。 需要申请或按当日安排参加的皇居参观,则有独立的路线与规定。
[宫内厅的东御苑说明](https://www.kunaicho.go.jp/visit/higashigyoen/index.html)列有公开日、入口与地图。 东御苑自1968年起开放,入园无需门票或事先预约,但有休园日和季节性的开放时间。
## 从大手门走进江户城的痕迹
大手门曾是江户城的重要正门,如今也是进入东御苑的入口之一。 沿路观察门的配置、护城河与石垣,再往本丸方向走,比单看某一座建筑更容易理解城的空间。
本丸的天守台,是江户城天守烧失后留下的石造基台。 天守在1657年的明历大火中烧毁,此后没有重建。 因此,在这里看到的是遗构,不是一座可以登楼参观的复原天守。
## 远看桥与橹
橹是城郭中的防御建筑。 巽橹、富士见橹与伏见橹有不同的位置与名称,不能把它们都叫作“二重橹”。 正门前的石桥和更里面的正门铁桥也应分别辨认,不要只凭常见照片就把眼前的桥一概称作二重桥。
## 留出一段步行时间
皇居周围看似开阔,入口之间却有距离。 出发前选好想看的区域,再确认开放日与出口;若从东京站往返,也把站内行走和广场停留算进时间。 只在外苑散步,与完整走过东御苑,会是两种不同分量的行程。
### 认识能与狂言:日本传统舞台艺术入门
URL: https://www.sakashita.page/noh-and-kyogen/
Last updated: 2026-10-24T01:30:00.000Z
第一次接触能乐,可以先从舞台上的两种节奏入门。 能以歌咏、舞与音乐展开人物的记忆和情感,狂言则常用对白与动作表现日常生活中的机智、误会和可笑之处。 两者共用能舞台,也有各自的剧目与表演方法。
能乐在2001年获联合国教科文组织宣布为人类口头和非物质遗产代表作,2008年列入人类非物质文化遗产代表作名录。 两个阶段的年份可见[联合国教科文组织的能乐条目](https://ich.unesco.org/en/RL/nogaku-00012)。
## 能:用有限的动作展开一个世界
能与狂言的形成,与传入日本的散乐及后来发展的猿乐有密切关系。 到室町时代,观阿弥、世阿弥等人的活动,推动了能的艺术形式与理论发展。 若想先了解这段历史,可以从[国立能乐堂的入门资料](https://www2.ntj.jac.go.jp/unesco/noh/en/history/history4.html)继续阅读。

展览中陈列的能乐装束
舞台上的主角称为“シテ”,与主角相对的角色称为“ワキ”。 地谣与伴奏的乐声共同支撑表演,观众听到的故事也不全由人物之间的对白交代。 慢慢移动的脚步、身体转向,以及扇子的使用,都可能承担情绪和场景变化。
能乐的角色分工与流派要分开看。 其中,主角一方的五个流派为观世、金春、宝生、金刚、喜多;[文化厅的技艺说明](https://online.bunka.go.jp/db/heritages/detail/215299)列出了这五流。 今天,女性能乐师也活跃在舞台上。 [能乐协会的问答](https://www.nohgaku.or.jp/guide/whywhat1)说明了女性演者的参与及仍然存在的角色限制。
## 面具:看角度与光线带来的变化

展柜中不同角色使用的能乐面具
能面常用来表现女性、老人、神灵、鬼等不同角色,但不是所有角色、所有演出形式都佩戴面具。 照片中的“小面”,常用于年轻女性的角色。 它的表情并非只能从静止的正面理解:演员轻微抬头、低头,面部的阴影就会改变,观众感受到的情绪也随之变化。
欣赏时,可以把面具、装束和动作一起看。 同一种面具也可能用于不同剧目,因此不必急着把每张脸与一个固定人物一一对应。 [能乐协会的面具介绍](https://www.nohgaku.or.jp/guide/%E8%83%BD%E3%81%AE%E9%9D%A2)适合在看展或观剧前浏览。
## 狂言:对白里的节奏与笑意
狂言常与能编排在同一场节目中,也可以作为独立的剧目欣赏。 主人与仆人之间的交锋、人物自作聪明后露出的破绽,往往通过对白、重复与身体动作逐渐显现。 它有自己的戏剧结构,不宜只当作两出能之间供观众休息的插曲。
多数狂言人物以演员本来的面貌登场,部分神、鬼、动物等角色会使用面具。 面对不熟悉的语言,先看懂人物各自想做什么,再注意他们怎样回应对方,会比逐句追赶台词更容易进入情节。
## 舞台:从松树背板看向出入口

背板上绘有松树的木造能舞台
能舞台没有依靠频繁换景来表现地点变化。 主舞台后方绘有松树的背板称为“镜板”;从侧后方延伸的“桥挂”,既是演员出入的通道,也是表演空间。 桥挂尽头的五色“扬幕”,隔开桥挂与后台的镜之间。
主舞台、桥挂以及周边座位之间的关系,会影响观众观看演员移动的角度。 购票前可以先看剧场座位图,再参考[能乐协会的舞台说明](https://www.nohgaku.or.jp/journey/nohgakudo%5Fsanpo/first)认识这些位置。
## 从《钵木》开始理解故事
《钵木》讲的是一个雪夜借宿的故事。 贫困的武士佐野常世接待旅僧,为了取暖,烧掉了珍爱的梅、樱、松盆栽。 旅僧实际是出家后的北条时赖;故事随后转到镰仓,常世应召前来,最终得到领地的赏赐。 剧中以款待、困境中的忠心与身份揭示推动情节,不能直接当作北条时赖生平的史料。 [能乐协会的剧目简介](https://www.nohgaku.or.jp/encyclopedia/program%5Fdb/hachinoki)提供了人物与故事梗概。
第一次观剧,不妨先读当晚剧目的简介,记住主要人物与故事发生的地点。 接着把注意力分给声音、动作与舞台空间,允许自己有些段落只听、只看,不必马上解释一切。 演出日期、字幕或讲解安排,可从[能乐协会](https://www.nohgaku.or.jp/)及各剧场的公告确认。
### 从江户东京博物馆看日本的城郭与住宅
URL: https://www.sakashita.page/edo-tokyo-museum/
Last updated: 2026-10-24T01:19:59.000Z
以前参观江户东京博物馆时,我在住宅模型前停留了很久。 城郭、武家宅邸和长屋被缩小以后,建筑的规模与空间安排变得容易比较,也让我开始留意:认识一座旧城,不能只看其中最高、最华丽的建筑。
本文保留的是那次参观引出的观察,配图则是相关建筑的照片,并非博物馆现有展品的完整记录。
## 从宫殿想到城市里的住宅

北京故宫博物院,宫殿建筑的相关照片
提到古代城市,我们很容易先想到宫殿与城郭。 它们在建筑史中十分醒目,却不能代表城里每个人的生活。 同一座城市中,还有官员与武士的宅邸、商人的店铺、普通人的住处,以及寺院、神社和道路。 把这些空间放在一起看,才能从建筑的外观走向城市的日常。
中日两国的建筑制度各有时代背景,不能用一套简单的住宅等级表互相套用。 对我而言,模型的价值也正在这里:它让“谁住在哪里、空间怎样使用”成为可以具体观察的问题。
## 江户城的天守与将军的御殿

丸龟城天守,日本城郭建筑的相关照片
江户城的营建不是单独造起一座高塔。 德川幕府动员各地大名参与城郭建设,城内外的道路、宅邸和街区也逐渐构成了政治中心的空间。
其中,天守是城郭的重要象征,御殿则承担居住、接见与政务等功能。 不能把最高的天守直接理解成将军日常起居的住宅。 1638年,德川家光时期的江户城天守建成;1657年,它在明历大火中烧毁,此后没有重建。 如今在皇居东御苑看到的是天守台,想了解建筑与遗址的关系,可参考[宫内厅的东御苑介绍](https://www.kunaicho.go.jp/visit/higashigyoen/structures-gardens/index.html)。
## 武家宅邸与长屋

长屋样式的建筑,连排住宅的相关照片
武家宅邸可以从门、围墙、院落和房屋的组合来观察,长屋则让人把目光转向连在一起的住户与紧凑的生活空间。 看住宅模型时,我更愿意比较入口在哪里、屋内怎样分隔、房屋与街道如何相接,而不是只把它们排成“豪华”与“简陋”的两端。
模型终究经过选择与复原,不能代替对所有江户住宅的概括。 但从一间住处的大小走到一条街,再走到整座城市,确实能让历史变得更容易理解。
## 再去博物馆之前
江户东京博物馆于2026年3月31日完成长期改修后的重新开放,馆方的[沿革](https://www.edo-tokyo-museum.or.jp/about/history/)记录了这次变化。 准备参观时,请以当前的展览介绍和开馆通知为准,不要把过去的参观记录当成现有展陈的逐项清单。
[江户东京博物馆官方网站www.edo-tokyo-museum.or.jp](https://www.edo-tokyo-museum.or.jp/)
### 宫岛半日游:严岛神社、海岸与商店街
URL: https://www.sakashita.page/miyajima-half-day-trip/
Last updated: 2026-10-24T01:09:59.000Z
宫岛与严岛是同一座岛的两个名称,位于广岛县廿日市市。 半天时间适合把行程放在码头、严岛神社和附近街区:先沿海岸看大鸟居,再走进神社,最后留一点时间吃饭、逛店。 若还想登弥山,就应另外留出时间,不要把山上行程硬塞进这条海岸散步路线。
## 先渡海,再开始步行
从JR宫岛口站或广电宫岛口站到码头后,可乘JR西日本宫岛渡轮或宫岛松大汽船上岛。 JR渡轮的[时刻表](https://jr-miyajimaferry.co.jp/timetable/)注明,单程航行约10分钟;连同步行、买票和候船,实际换乘还要留出余量。 选择船公司时,同时确认返程码头、票券适用范围和当天班次。

在濑户内海航行的宫岛渡轮

停靠在宫岛码头的红白色渡轮
一条便于调整的半日路线是:
1. ⛴️ 宫岛口乘船,到宫岛码头后领取地图。
2. 沿海岸步行,经过平清盛像一带,从岸边看大鸟居。
3. 进入严岛神社,慢慢走过回廊。
4. 在表参道商店街或町家通附近午餐、逛店。
5. 按剩余时间选择交流中心、丰国神社的千叠阁,或红叶谷公园,之后返回码头。
这些停留点不必全部打卡。 想看建筑,可以多留时间给神社与千叠阁;想轻松散步,就把午餐和海边的停留放宽。 [观光协会的路线页](https://www.miyajima.or.jp/course/course.html)另有三小时、半日与一日方案,方便按体力和兴趣取舍。
## 大鸟居与海上的回廊

立于海中的严岛神社大鸟居
现在的大鸟居重建于1875年。 按照严岛神社的[修缮说明](https://www.itsukushimajinja.jp/jp/images/040%5Fpage/construction/pamphlet%5Fhp.pdf),若以平清盛资助营建时的鸟居为初代,它是第九代。 两根主柱前后各有辅助柱,柱下的地基也经过木桩加固。 鸟居依靠自重保持稳定,并不是浮在海面上的建筑。
潮水改变时,鸟居与回廊周围的景象也会变化。 想看海水环绕建筑的样子,可以先查潮汐;低潮时则会看到露出的滩地。 不要因为照片里有人走近鸟居,就认为任何时段都能采用同样的走法。

潮水环绕的严岛神社朱红色回廊
严岛神社的特色,在于朱红色建筑、前方海面与背后山林形成的整体景观。 1996年列入世界文化遗产的区域,包括神社建筑、前方海域和背后的森林,并不等于整座宫岛都在遗产区之内。 [宫岛观光协会的遗产介绍](https://www.miyajima.or.jp/pickup/heritage.html)说明了这一范围。

宫岛五重塔与远处的濑户内海
## 看大木杓,也换一个角度看街区

宫岛交流馆的入口与巨大木杓展示

绘有严岛神社鸟居图案的祈愿木杓
木杓既是盛饭的日用品,也是宫岛有代表性的工艺品与吉祥物。 交流中心外的大木杓长7.7米,1983年制作完成,1996年为纪念严岛神社列入世界遗产而在商店街展出,2021年移至这里。 制作与迁移经过可见[观光协会的介绍](https://www.miyajima.or.jp/sightseeing/ss%5Fshamoji.html)。
这处设施在2021年开放,过去以“etto宫岛交流馆”的名称为人熟悉。 除了大木杓,也可以看看公共空间,从展望室换个角度观察屋顶、五重塔与海岸。 开放区域和休馆安排请查[交流中心官网](https://www.city.hatsukaichi.hiroshima.jp/site/miyajimakoryu/),另有一篇[交流馆的参观记录](https://www.sakashita.page/miyajima-etto-cultural-center/)可一起阅读。

从山坡下仰望千叠阁与五重塔
## 把午餐留给商店街

宫岛红叶堂的店面与外带窗口

店员递来刚做好的红叶馒头点心
表参道商店街聚集了点心、土产和餐饮店;靠山一侧的町家通,则适合稍微离开热闹的人流,慢慢看街屋。 红叶形状的点心、牡蛎和牡蛎咖喱面包,都是这次散步照片里的食物。 与其边赶景点边随便吃几口,不如留一段完整时间坐下来吃饭,再挑一种喜欢的点心。

宫岛街头现开牡蛎的摊位

搭配柠檬享用的宫岛牡蛎
照片里不同产地的牡蛎和当时的菜单,是这趟旅行的记录。 之后来访时,食材、做法与供应情况仍要看当天店内的说明。

宫岛街头品尝的牡蛎咖喱面包

宫岛牡蛎咖喱面包店的招牌与店面
春天的花、秋天的红叶与一年中的祭典,会让同一条路线呈现不同的面貌。 若为某个活动而来,先核对[观光协会的年度活动表](https://www.miyajima.or.jp/calendar.php),再决定停留多久。 普通日子也有普通日子的看法:从船上看岛,走近海边的建筑,再带着一点街区里的味道返程。
### 宫岛的etto交流馆
URL: https://www.sakashita.page/miyajima-etto-cultural-center/
Last updated: 2026-10-24T01:00:00.000Z
宫岛不只有严岛神社与商店街。 旧宫岛町公所改建而来的交流设施,在2021年开放,曾以“etto宫岛交流馆”的名称为人熟悉。 这里既供当地居民交流使用,也有旅行者可以参观的空间。
## 从一楼走到展望室

交流馆一楼的公共空间

二楼的展厅与休息空间
与热闹的商店街相比,馆内让人有机会换一个角度认识宫岛的日常。 参观时要留意哪些是公共开放区域,哪些房间正在举办活动。

三楼展望室

从交流馆一带眺望五重塔与濑户内海
展望室可眺望宫岛的屋顶、五重塔及濑户内海方向。 先在街道里看建筑,再从较高的位置看它们与海岸的关系,是这处停留点的乐趣。
## 宫岛的大木杓

交流馆外展示的宫岛大木杓
木杓是宫岛的代表性工艺品,也被当作寄托吉祥愿望的物件。 馆外的大木杓长7.7米、最大宽2.7米、重2.5吨,尺度远远超出日常的饭勺。 根据[宫岛观光协会的介绍](https://www.miyajima.or.jp/sightseeing/ss%5Fshamoji.html),它在1983年完成,1996年为纪念严岛神社列入世界遗产而在商店街展出,2021年迁至交流中心。
把小小的日用品放大到这样的尺寸,也让人更直观地注意到宫岛与木杓工艺的关系。 想进一步了解木杓的由来,可以结合岛内的工艺展示和店铺说明阅读。
开放区域、休馆与设施名称可能调整,出发前请查看[廿日市市的交流中心信息](https://www.city.hatsukaichi.hiroshima.jp/site/miyajimakoryu/)。
### 丸龟城与团扇制作
URL: https://www.sakashita.page/marugame-castle-and-fans/
Last updated: 2026-10-24T00:49:59.000Z
在丸龟城,可以先抬头看石垣,再把目光移到手里的竹骨团扇上。 一边是沿山势叠起的城郭,一边是需要细细加工的日常用具,两种尺度完全不同的工艺,让这一天有了清楚的重点。
## 从城门往天守走

欢迎游客来到丸龟的吉祥物看板
丸龟城位于香川县丸龟市,天守属于日本现存十二天守之一。 城郭始建于生驹氏时期,此后经历废城、再筑与山崎氏、京极氏时期的整备,今天看到的建筑与石垣并不是同一次施工留下的整体。 想梳理沿革,可参考[丸龟市的城史介绍](https://www.city.marugame.lg.jp/site/castle/2930.html)。

丸龟城沿山势叠起的高大石垣
这里醒目的是多层石垣与小山共同形成的高度。 常见的“约60米”说法指层叠后的整体高差,不能理解成眼前每一面石墙都有60米高。 石垣下部较缓、上部较陡的曲线也值得细看,不只是为了记住一个高度排名。
登城需要走坡路,适合穿便于行走的鞋。 若想进天守,另查[官方观览说明](https://www.city.marugame.lg.jp/site/castle/7454.html);旧文中的门票与“全年绝不休馆”说法已经不适合作为出行依据。 石垣也有修复工程,参观以现场开放路线为准。
## 把团扇做成一件旅行纪念
丸龟团扇以竹骨和纸面为特色,1997年被指定为日本国家传统工艺品。 [丸龟市的工艺介绍](https://www.city.marugame.lg.jp/page/3056.html)说明了它与金刀比罗参拜土产、当地产业发展的关系。 这项指定所指的传统竹扇,也不应与所有塑料团扇的产量统计混成一个概念。

在工作人员指导下体验丸龟团扇制作
这次在工作人员的指导下体验制作,才发现一把看似简单的团扇,纸面与竹骨的配合也有不少细节。 把材料放稳、按说明完成手里的步骤,比单纯买一件纪念品更容易留下印象。 体验课程通常只涵盖部分工序,并不等于从一根竹子开始完成全部传统制作过程。

绘有金字标志的红色竹骨团扇
做好的红色团扇上有金色标志,正好把这段经历带回家。 若准备安排同类体验,请先向[城内观光案内所](https://www.city.marugame.lg.jp/site/castle/list16-171.html)确认当前的场地、预约与费用,不沿用过去的价格和截止时间。
## 还想了解一点当地历史

丸龟市立资料馆的正面建筑
有余裕的话,可以把丸龟市立资料馆接在登城之后。 馆内通过城郭、藩政、民俗等资料介绍当地历史,也展示团扇制作相关用具。 收藏数量与当日实际展出的件数是两回事,不必为了看完一个总数而给自己设定目标。
[资料馆的开放与展览说明](https://www.city.marugame.lg.jp/page/2857.html)可以在出发前再看一次。 从石垣到竹扇,再到馆里的生活用具,这条短路线让丸龟不只是一座远远望见天守的城市。
### 道后温泉之旅
URL: https://www.sakashita.page/dogo-onsen/
Last updated: 2026-10-24T00:39:59.000Z
道后温泉位于爱媛县松山市。 从电车站走向温泉街,木造的道后温泉本馆是最醒目的地标之一。 这里适合把建筑观察、泡汤与街头散步放在一起,不必只把它当成旅馆住宿的附属行程。
## 一座仍在使用的历史浴场
本馆以1894年建成的神之汤本馆栋等建筑为核心,经过增建,形成今天层叠而富于变化的木造外观。 它被指定为日本重要文化财,却仍是一处可以使用的公共浴场。 参观时,屋顶、窗户与不同方向的立面,都值得绕着建筑慢慢看。
馆内的神之汤、灵之汤和休息空间,依不同方案开放。 皇室专用浴室“又新殿”具有独立的历史与参观安排,并非普通游客可以自由使用的私人浴池。 具体方案应查看[道后温泉官方网站](https://dogo.jp/),不宜照搬旧游记的票价或使用范围。
## 文学与温泉街
夏目漱石的小说《哥儿》与松山、道后温泉有很深的联系。 把小说中的场景与眼前的街道对照,是另一种散步的方式。
温泉街上还有商店与饮食店,可以留意爱媛的柑橘制品、地方点心和海鲜料理。 白天看建筑细节,傍晚再看灯光下的街道,会有不同的观感。
## 怎样安排行程?
从松山市中心可搭乘电车到道后温泉站,再步行前往本馆。 如果当天还要去松山城,建议把移动、排队与泡汤后的休息时间一起算进计划。 封面使用的是保存修理期间拍摄的照片,不能据此判断现在仍在施工。
### 广岛和平纪念公园:遗址、资料馆与追悼空间
URL: https://www.sakashita.page/hiroshima-peace-memorial-park/
Last updated: 2026-10-24T00:29:59.000Z
广岛和平纪念公园位于市中心的中岛地区。 今天的绿地、河岸和纪念设施所在之处,曾经是人们居住、工作和往来的街区。 1945年8月6日的原子弹爆炸摧毁了这里,战后的公园建设则把这片土地变成了保存记忆、追悼死者和思考和平的公共空间。
参观时,可以把原爆圆顶馆、广岛和平纪念资料馆和公园里的纪念碑看作相互连接、作用又不同的部分。 不要只把它们当成几处需要拍照的地标,也不必预设自己一定会获得某种感受。 留出阅读说明和安静停留的时间,比赶完全部地点更合适。
## 原爆圆顶馆:保留下来的遗址
原爆圆顶馆的前身是广岛县产业奖励馆。 爆炸后,部分墙体和穹顶骨架残存下来;眼前的建筑是一处经过持续保护的被爆遗址,并非完好保存的旧馆。 1996年,它以广岛和平纪念碑的名义列入世界文化遗产。 遗产范围及其历史意义,可见[联合国教科文组织的说明](https://whc.unesco.org/en/list/775/)。
从河岸看遗址,再读附近的说明牌,会更容易理解它与周围城市的关系。 参观以外围观看为主,不要把围栏内的遗构当成可以进入的普通建筑。
## 广岛和平纪念资料馆:从实物认识人的经历
资料馆通过遗物、照片、证言等材料介绍原子弹爆炸及其后果。 这些材料不只是说明一场爆炸有多大威力,也记录了具体的人怎样生活、受伤、失去家人,以及怎样面对此后的岁月。 看展时,可以放慢阅读速度,留意物品背后的姓名与经历。
资料馆与公园的开放空间不同,需要按馆方安排入馆。 出发前请在[资料馆官网](https://hpmmuseum.jp/)确认开放时间、购票方式和临时通知。 展览中有令人沉重的内容,按自己的状态安排停留,必要时休息以后再继续。
## 在公园里连接这些记忆
原爆死没者慰灵碑、和平之火和原爆之子像等设施,各自承担着追悼与表达和平愿望的作用。 它们并不是重复讲述同一段说明。 读一读碑前的介绍,再看人们留下的花和纸鹤,可以把抽象的历史叙述与个人的悼念联系起来。
公园一带原有街区的痕迹,也值得留意。 广岛市的[中岛地区历史介绍](https://www.city.hiroshima.lg.jp/atomicbomb-peace/1036664/1021115/1003095.html)提供了另一条理解路线:先认识这里曾经是什么地方,再看战后的纪念空间如何形成。
## 怎样安排一次参观
可以先沿河岸看原爆圆顶馆,再步行进入公园,经主要纪念设施走向资料馆。 也可以先看展览,再回到户外辨认刚刚读到的地点。 两种顺序都可以,重点是给资料馆留出完整的一段时间,不把它夹在匆忙的换乘之间。
每年8月6日前后的纪念活动,与普通日期的参观安排有所不同。 若准备在这段时间前往,应单独查看广岛市发布的活动与交通通知。 平日散步时,也请尊重正在献花、祈祷和追悼的人,把拍摄与交谈控制在不打扰他人的范围内。
### 姬路城的历史与参观体验
URL: https://www.sakashita.page/himeji-castle/
Last updated: 2026-10-24T00:19:59.000Z
从姬路站北口望向大手前通,街道尽头的姬路城很容易辨认。 白色的外墙与层叠的屋顶,使它有了“白鹭城”的别称。 真正走进城内,值得看的不只有远处拍下的天守全景,还有通往天守的一段段道路、门与石垣。
## 从城门走向天守
现存的大天守主要形成于池田辉政在1601年至1609年主持的大规模改建。 大天守与三座小天守相连,是理解连立式天守结构的代表。 姬路城于1993年列入世界文化遗产。
建筑从外面看是五重屋顶,内部则有地上六层与地下一层。 “外观几重”与“内部几层”是不同的计数方法,参观时可以留意窗户、楼层和屋顶怎样对应。
## 把城内步行也算进计划
城内有坡道、狭窄的楼梯和需要脱鞋进入的空间。 这并不是乘电梯登上一座现代展望楼的行程,鞋子、随身行李和同行者的体力都会影响参观节奏。 人多时也可能需要在天守前等候,应留出余地。
走完城内后,若还有时间,可以考虑相邻的好古园,再回到大手前通吃饭或休息。 如果只想看外观,也能围绕城前广场与护城河安排较轻松的散步。
## 交通与参观信息
姬路站至城一带可步行,也可利用当地巴士。 过去游记里的环城巴士、一日券和折扣,不应直接当成现在仍有效的方案。 出发前请查看[姬路城官方便览](https://www.himejicastle.jp/)中的开放时间、票价、交通和参观提醒。
### 香川高松经典一日游
URL: https://www.sakashita.page/takamatsu-day-trip/
Last updated: 2026-10-24T00:09:59.000Z
高松适合把庭园、城址与港口放在同一天。 不过,栗林公园与高松站、玉藻公园所在的港口区域并不相邻,先分清这两片空间,行程会容易安排许多。
如果喜欢庭园,可以把上午留给栗林公园,下午再回到城址、博物馆与海港。 想逛商店街的话,则在傍晚按体力增加一段步行。
## 玉藻公园:海水护城河与近代庭园

玉藻公园入口的园名石碑

披云阁的建筑与庭木

高松城旧址的木造城门
玉藻公园以高松城遗址为核心。 城郭引入海水作为护城河,与港口的关系很紧密,走近水边,便能看出它与山城不同的环境。

护城河上的小船与石垣
游船可以从水面认识城郭,但开放日期、接待与天气条件要另查。 岸边散步与登上天守台,也能从不同高度看水面和城址。

公园内展示的小型盆栽

高松城天守台的石垣与步道
公园里的披云阁与庭园,又有近代的一层历史。 现有披云阁在20世纪初营建,庭园也在1914年至1917年前后整备,不能把这里所有景观都当作原封未动的江户城郭。
[高松市:披云阁庭园www.city.takamatsu.kagawa.jp](https://www.city.takamatsu.kagawa.jp/smph/kurashi/kosodate/bunka/bunkazai/shiteibunkazai/meisho/hiunkaku.html)
## 栗林公园:留足一段散步时间

栗林公园池塘上的拱形桥

池中聚集的锦鲤

池畔茶屋与山林的倒影
栗林公园在生驹家、高松松平家等历代整备下逐渐成形,1745年完成主要营建,此后仍持续修筑。 1875年向公众开放,1953年被指定为日本国家特别名胜。
与其只按一条最快路线走完,不如选一个方向,慢慢观察池面、桥、松树和远山怎样连在一起。 从低处到稍高的观景点,同一片池塘也会呈现不同的构图。

绿树环绕的宽阔园路

园内标有石菖的水边花圃

绿树之间的池塘与石景
园内南庭与北庭各有景观,也有茶屋和休息空间。 想喝茶、乘船或用餐,需要另看各设施当日的接待时间。 若只剩很短时间,可以减少其他景点,给庭园留一个完整的上午。
[栗林公园:历史与游览信息www.my-kagawa.jp](https://www.my-kagawa.jp/ritsuringarden/feature/ritsuringarden/garden)
## 香川县立博物美术馆

香川县立博物美术馆的入口
香川县立博物美术馆兼有历史与美术展示,位于玉藻公园一带。 如果想了解香川的文化背景,可以在这里选择一个展览主题,接续庭园或城址的参观。
常设展也会换展,收藏过某位画家的作品,并不表示每次都能看到同一批画。 先看当前展览与开放日,再决定停留多久。
[香川县立博物美术馆:展览信息www.kmuseum.pref.kagawa.lg.jp](https://www.kmuseum.pref.kagawa.lg.jp/)
## 阳光海港与傍晚的濑户内海

高松港日落时的天空与海面

港边的时钟塔

阳光海港的建筑与松树
高松站与高松港相近,码头连接小豆岛、直岛、女木岛与男木岛等岛屿。 如果另有跳岛计划,应先查目的地、码头与船班,给买票和登船留下时间。
只留在市区,也可以沿海边散步,看看海面与来往船只。 防波堤与红色灯塔方向的步行距离,要按天气和返程时间决定。

夜间亮灯的高松地标塔

高松市区建筑内的彩伞装饰
高松地标塔的29层设有展望空间,但有独立营业日程。 想在高处看景,应先核对开放时段;它并非每天都能从白天一直待到深夜。
[地标塔29层:展望空间日程maripla.jp](https://maripla.jp/space/)
## 逛进中央商店街

丸龟町商店街的玻璃穹顶

夜间亮灯的南新町商店街入口

这次旅行买下的毛巾伴手礼
高松市中心有多条相连的拱廊商店街,丸龟町、南新町等各有不同店铺。 下雨时可以在有顶棚的路段走走,也适合安排晚餐和伴手礼购物。
不必为了走完全长而赶路。 先选一间想吃的店,再沿附近街道看看,往往更容易记住这一晚的样子。
## 一天的安排
- 🚃 上午:按出发地点选择铁路或公交前往栗林公园。
- 🚶 下午:回到高松站与城址区域,选择玉藻公园和一处展览。
- 🌇 傍晚:在港口看天色变化,再按体力到商店街吃饭。
若当天还要乘船去岛上,就减少市区景点。 庭园、城址与跳岛各有需要停留的时间,分成两天会更从容。
### 松山一日游:城郭、街区与道后温泉
URL: https://www.sakashita.page/matsuyama-day-trip/
Last updated: 2026-10-24T00:00:00.000Z

保存修缮期间的道后温泉本馆
松山的经典景点,可以沿着路面电车串成一天。 上午上松山城,午后看庭园或逛街,傍晚去道后温泉;想看美术展,也可以从购物时间里留出一段。
## 先分清两个车站

松山街头金色耳朵造型的纪念碑
JR松山站与伊予铁的松山市站是不同车站。 机场、铁路与市内电车的换乘,要按实际抵达地点安排。 伊予铁有多种观光票券,购买前看看当天会乘几次车,以及是否包含所需的路线。
[伊予铁:车票与交通指南www.iyotetsu.co.jp](https://www.iyotetsu.co.jp/railbus/ticket/)
## 松山城:从山坡来到城郭

通往松山城山上的空中缆车
松山城位于市中心的胜山。 从东云口方向可以选择空中缆车或单人吊椅,上山后仍要步行一段,才会抵达主要城郭。 天气和检修可能影响运行,登城时间也要把步行算进去。

从松山城高处眺望城郭与松山市区
加藤嘉明于1602年开始筑城。 今天的大天守是江户时代后期重建并保存至今的建筑,也是日本现存十二天守之一。 走入城郭时,石垣、门与连廊的组合,比远看一座天守更能展现这座城的规模。

松山城石垣上延伸的木造长墙
登高后可以眺望松山市区。 能见度好时,远方的山地和濑户内海方向的景色也会逐渐展开。 城内有楼梯,参观时穿便于行走的鞋会更合适。
[松山城:登城、交通与开放信息www.matsuyamajo.jp](https://www.matsuyamajo.jp/)
## 二之丸史迹庭园:用水景读旧宅布局
二之丸曾是藩主宅邸所在,今天的庭园通过水池、植物与铺地表现旧建筑的布局。 在看过山上的城郭之后,再来到这里,能认识城内生活空间的另一面。

二之丸史迹庭园石壁间流下的瀑布

二之丸史迹庭园的池塘与石景

以水池呈现旧宅布局的二之丸史迹庭园
水流、石景与整齐分隔的池面,让庭园很有自己的性格。 如果喜欢庭园,可以在这里多留一点时间,再从山脚前往市中心。
庭园出土过刻有姓名的俄国金币,相关人物的故事成为当地结缘叙事的一部分。 这里也常被用作婚纱摄影场地,参观时可以留意园方介绍,但不必把传说当成已经完整考证的爱情史。
[二之丸史迹庭园介绍www.matsuyamajo.jp](https://www.matsuyamajo.jp/guide/ninomaru.html)
## 大街道:在拱廊下慢慢逛

松山大街道商店街的拱顶入口
大街道是松山市中心的购物街。 拱廊下有服饰、餐饮和伴手礼店,从电车站转进来就能步行逛一段,也适合在景点之间安排午餐。

大街道商店街上方的黄色装饰

松山三越前的广场与街景
夏季夜市等活动有各自的举办日期。 平日来访也不必只追着活动走,沿街看看店铺,再找个地方坐下,就能把登城后的节奏放缓。
## 松山市站的屋顶摩天轮

伊予铁高岛屋屋顶的摩天轮
伊予铁高岛屋位于松山市站,屋顶的摩天轮是认识城市方位的一种方式。 从地面街道来到高处,可以重新找找刚刚走过的城山和街区。

高岛屋摩天轮的黄色主题车厢

从摩天轮上眺望松山城方向
透明车厢等乘坐选项、最晚受理与停运安排,请以当天说明为准。 如果想看夜景,记得同时确认百货商店与摩天轮各自的营业时间。
[伊予铁高岛屋:摩天轮与营业信息www.iyotetsu-takashimaya.co.jp](https://www.iyotetsu-takashimaya.co.jp/business-hours-parking/)
## 道后温泉:车站、机关钟与老浴场

道后温泉站的复古站房与少爷列车
来到道后温泉站,复古站房和少爷列车便把旅途带入另一种气氛。 少爷列车有独立的运行安排,普通市内电车与它的票券也需分开确认。

道后温泉商店街内的纪念拍照看板

道后温泉站前的少爷机关钟
车站前的机关钟与商店街可以连着走。 小说《哥儿》与当地文学记忆,也融入了车站、钟楼及街上的小物之中。

推动道后温泉近代发展的伊佐庭如矢纪念像
### 道后温泉本馆

修缮期间的道后温泉本馆
本馆的神之汤本栋于1894年改建,1994年成为日本国家重要文化财。 照片记录的是保存修缮期间的外观;本馆已于2024年7月11日恢复全馆营业,保存修缮工程于同年12月完成。
不同的入浴与休息方案,包含的空间和使用时间不同。 想在这里结束一天的散步,先选择适合自己的方案,再留出候位和休息的时间。

到访时飞鸟乃汤泉中庭铺设的蜷川实花作品
道后还有椿之汤和飞鸟乃汤泉等浴场。 照片中的彩色中庭属于到访当时的艺术展示,再访时看到的布置可能不同。
[道后温泉:浴场与当前利用信息dogo.jp](https://dogo.jp/)
## 爱媛县美术馆:按展览决定停留

爱媛县美术馆前的展览告示

爱媛县美术馆的玻璃外墙与入口
爱媛县美术馆位于城山公园一带,收藏与爱媛有关及国内外艺术家的作品,并举办不同主题的展览。 如果当期展览正合兴趣,可以把午后留给美术馆,减少商店街或摩天轮的停留。
[爱媛县美术馆官方网站www.ehime-art.jp](https://www.ehime-art.jp/)
## 一天怎样取舍
- 🚠上午:上松山城,参观天守与城郭;下山后按兴趣加入二之丸庭园。
- 🚶午后:在大街道吃饭、逛街,或改去爱媛县美术馆。
- 🚋傍晚:乘市内电车到道后温泉,散步后泡汤。
摩天轮适合与松山市站一带的活动结合。 若还要泡汤和认真看展,把部分景点留到第二天,通常更能享受松山缓慢的步调。
### 高知五台山与桂滨:MY游巴士一日之旅
URL: https://www.sakashita.page/kochi-godaisan-and-katsurahama/
Last updated: 2026-10-23T23:49:59.000Z
五台山的视野、牧野植物园的温室、竹林寺的塔,再到桂滨的太平洋海景,可以串成高知的一日路线。 不自驾时,MY游巴士提供了连接这些地方的选择。 这条路线既有需要慢慢走的园区,也有适合短暂停留的观景点,先决定自己最想看什么,会更容易安排一天。
## 用MY游巴士连接山与海
MY游巴士从高知站南侧的“こうち旅広場”一带出发,连接五台山和桂滨方向。 购票时要分清只到五台山的票与包含桂滨的票,并核对当天时刻表。 有乘车券也不代表已经预约座位,遇到满员或交通延误时,要为下一段行程留出调整空间。

MY游巴士停靠车站台
可以按下面的顺序取舍停留点:
1. 🚌 高知站出发,前往五台山。
2. 在观景处看市区与港湾,再前往牧野植物园。
3. 步行参观附近的竹林寺。
4. 🚌 继续乘车到桂滨,散步、吃饭后按返程班次回市区。
如果想仔细看植物园,就把它作为主要行程,缩短其他停留;若以寺院与海景为主,也可以只选园内最感兴趣的区域。 出发前同时记下从桂滨返回的班次,不要等逛完以后才查末班车。
[MY游巴士:路线、票券与最新运行公告kochi-tabi.jp](https://kochi-tabi.jp/my-bus/)
## 五台山:先看看高知的位置

从五台山眺望高知市区与港湾

五台山观景处树梢外的高知市景
从五台山的观景处望出去,能看到城市建筑、河流与浦户湾一带。 先在高处看一眼,再去下面的街区或海边,会更容易理解高知市区与港湾的关系。 照片里的树梢、屋顶与水面,也让这一站有了不同于单个景点打卡的尺度。
## 牧野植物园:把时间留给观察

牧野植物园内的牧野富太郎铜像

牧野植物园温室中展示的巨大叶片
高知县立牧野植物园于1958年4月开放,纪念出生于高知的植物学家牧野富太郎。 园地依五台山的起伏展开,除了植物展示,也有介绍牧野生涯与研究工作的空间。 [园方的介绍](https://www.makino.or.jp/multilingual/?lang=en)说明了开园经过,以及研究、保育和教育等工作。
走进园区,可以先选一条自己感兴趣的路线:看露地植物、读人物展示,或把重点放在温室。 山地园区的步行和高差也需要时间,不宜只按地图上的直线距离估算。

温室内沿树干生长的紫色花朵

牧野植物园温室里的兰花与绿植
温室里,大叶片、花序与攀附植物的生长方式都值得停下来看。 读一读植物标签,也能把熟悉的食材与原来的植物联系起来。 例如,可可是巧克力的原料,咖啡则来自咖啡属植物,两者是不同的植物。 当季能看到什么花,以及园区的开放与入园安排,可在[植物园官网](https://www.makino.or.jp/)查询。
## 竹林寺:在树木之间看塔与庭园

竹林寺的朱红色五重塔
竹林寺是四国八十八所灵场的第三十一番札所,本尊为文殊菩萨。 参观时,可以把本堂、庭园和五重塔分开看,不必急着一次走过。 树木、石阶与朱红色的塔放在一起,也是这里很有辨识度的景象。
现在的五重塔建于1980年,采用镰仓时代初期的建筑样式。 看到传统样式时,仍要把建筑形式与实际建造年代分开;[四国八十八所灵场会的寺院介绍](https://88shikokuhenro.jp/31chikurinji/)列有塔与庭园的说明。 寺院内不同区域的参观条件,以及庭园、宝物馆的开放安排,应按当天公告选择。
## 桂滨:在太平洋边收尾

桂滨高台上的坂本龙马铜像

桂滨海岸拍打礁石的海浪
桂滨面向太平洋,沙滩、松树和海浪构成了与五台山不同的开阔风景。 可以沿园内步道散步,看看坂本龙马铜像;若想多了解他的生平,再为附近的纪念馆单独留出参观时间。 这里适合看海,但桂滨禁止游泳,公园在[利用须知](https://katsurahama-park.com/faq/)中也说明了海浪变化与相关要求。

桂滨餐厅的鲣鱼拉面与盖饭套餐

桂滨园区色彩缤纷的墙面插画
园区里的“桂浜 海のテラス”有餐饮与购物空间,适合在返程前坐下来休息。 照片中的餐点属于当时的记录,具体店铺、菜单与开放安排可查[桂滨公园官网](https://katsurahama-park.com/top/)。 从山上看城市,在园里看植物与建筑,最后走到海边,这一天的几个停留点各有自己的看法。 给最喜欢的一站多留一点时间,就不必让巴士路线变成匆忙的打卡清单。
### 青森浅虫温泉与扇贝广场
URL: https://www.sakashita.page/asamushi-onsen-and-hotate-square/
Last updated: 2026-10-23T23:39:59.000Z
从青森市区出发,浅虫温泉是一次不必住一晚也能安排的海边旅行。 这次我和朋友把行程分成两段:先在浅虫泡汤、逛市场,再往平内方向吃午餐、看扇贝展览。 电车、海景与当地食物接在一起,节奏比赶着换几个景点更轻松。
## 从青森乘电车到浅虫温泉

浅虫温泉附近水边的桥与冬日阳光
浅虫温泉位于青森市东侧,面向陆奥湾。 我们从青森站乘青い森铁道的电车,约20分钟抵达浅虫温泉站。 [青森县的观光介绍](https://aomori-tourism.com/spot/detail%5F101.html)也列出了这条到达路线;实际班次与车费可再从[铁路公司的车站页](https://aoimorirailway.com/lang%5Fjp/station%5Flist/asamushi/)确认。

浅虫温泉站的站房
车站附近就有海岸与温泉设施,适合把主要时间留给步行和停留。 若当天还要返回青森,先记下回程班次,再决定午餐以后能走多远,会更从容。
## 在ゆ~さ浅虫看海、泡汤
我们选择的是车站附近的道之站“ゆ~さ浅虫”。 日本的道之站通常结合休息、购物与地方信息,这里还设有可以望向陆奥湾的展望浴场。 那天从浴场的大窗望出去,蓝天、海面与汤之岛连在一起,是这趟停留最鲜明的景象。

浅虫温泉海岸外的陆奥湾与小岛
泡汤前,先按设施要求放好鞋子和行李,在洗浴区洗净身体后再入池。 具体用品、入浴费用和开放时间,请查看[ゆ~さ浅虫的设施公告](https://www.yu-sa.jp/contents/)。 若只想在海边散步,也可以把这里作为买土产与短暂休息的一站。
## 在市场里挑一点当地食物

在当地市场购买的葡萄
我们在商店买了久慈良饼和苹果点心,又被旁边市场里的蔬果吸引,进去逛了一圈。 摊位上有盆栽、蔬菜、水果,以及腌菜、山菜等加工品。 最后带走了一些腌菜、苹果和葡萄;葡萄个头不大,吃起来却很甜。
这类市场的乐趣,在于看到当天实际摆出来的东西。 不必预先把购物清单排满,留一点空间给当季食材,也留意之后是否方便携带、保存。
## 到平内吃扇贝午餐

平内扇贝广场的寿司与扇贝铁板烧套餐
接下来,我们去了平内町的扇贝广场一带。 餐饮设施“ホタテ一番”与销售、展示扇贝的广场相邻,安排午餐时要分清餐厅和商店各自的开放时间。 店家公布的到达方式包括从浅虫温泉站步行约20分钟,或乘出租车约5分钟;选择步行时,也要把天气和路况考虑进去。

平内扇贝广场旁的钟塔
那次用餐时,餐厅宽敞,播放着轻柔的钢琴曲。 我们先在机器上选餐、购买餐券,再交给店员。 我点了寿司与扇贝铁板烧的组合,朋友选了扇贝培根意面,两个人都吃得很满足。
照片和这段描述记录的是当时吃到的料理。 现在店家的[菜单与公告](https://hotateichiban.com/)列有不同海鲜和当地食材的组合,其中活扇贝料理是否供应,要看进货情况。 如果专程为某道菜而来,先确认当天供应,比直接照着旧照片点餐更可靠。
## 从餐桌再走到展示区

平内扇贝广场的儿童游乐设施

展示不同贝壳的扇贝知识展区
吃完以后,我们又看了广场和展示空间。 当时的展览既介绍扇贝的种类与养殖,也有津轻三味线演奏家高桥竹山的相关资料。 把刚吃过的食材,与当地的海、养殖和人的生活联系起来,是这里比单纯一顿午餐多出来的收获。
[平内町的设施介绍](https://www.town.hiranai.aomori.jp/kanko%5Fbunka%5Fsports/kanko/1/1491.html)列有扇贝广场的位置与利用信息。 销售与配送则应查看[经营方的公告](https://www.hotatehiroba.com/);近年的通知多次提到进货不稳定,不能把过去的价格或可配送状态视为长期承诺。
那天我们在广场稍作休息,再回到浅虫温泉站,乘电车返回青森。 如果时间有限,只安排浅虫泡汤与海边散步也足够;若想把午餐接到平内,就为往返和餐厅营业时间单独留出余量。
### 山形鹤冈经典一日游
URL: https://www.sakashita.page/tsuruoka-day-trip/
Last updated: 2026-10-23T23:29:59.000Z
鹤冈位于山形县庄内地区,市中心保存着城下町、近代洋馆与宗教建筑的不同面貌。 如果只有一天,可以把鹤冈公园周边作为步行核心,再按兴趣走进博物馆、文化会馆与教堂。
理解这里的历史,先分清酒井家的家族世代与庄内藩主世代。 1622年进入庄内、成为酒井家在当地首任藩主的是酒井忠胜;德川家康时代的武将酒井忠次,是他的祖父。
## 致道博物馆:建筑、庭园与庄内生活

致道博物馆内的旧西田川郡役所
致道博物馆于1950年设立,与旧庄内藩主酒井家的捐赠有关。 馆内汇集历史建筑、庭园、民俗资料与美术展览,可以从不同角度认识庄内。

日式建筑内的书画与室内陈设

致道博物馆内不同年代的建筑
旧西田川郡役所、旧鹤冈警察厅舍与旧涩谷家住宅,各自呈现不同用途和时代的建筑形式。 从洋式办公建筑走到民居,再看与藩主有关的空间,参观的内容会自然展开。

酒井氏庭园内的树木与石景

庭园与旧民居相邻的博物馆景观
庭园适合慢慢看树木、石组与建筑的关系,民具展示则能把目光转向生产和日常生活。 馆藏数量、当期展品与可进入的建筑范围各有区别,计划参观时可以先看当前展览。
[致道博物馆:建筑、展览与开放时间www.chido.jp](https://www.chido.jp/)
## 庄银TACT鹤冈:当代城市的一面

庄银TACT鹤冈层叠起伏的屋顶
离开历史建筑,也可以看看庄银TACT鹤冈文化会馆。 这座由妹岛和世等设计团队参与的建筑,于2018年正式开馆,层叠的屋顶让较大的演出设施与周围街区形成不同尺度的连接。
入口大厅有面向公众开放的时段,演出厅则需要配合活动或见学安排。 若想进到更深处参观,应先确认是否有适合的场次或导览。
[鹤冈市:文化会馆与公共空间www.city.tsuruoka.lg.jp](https://www.city.tsuruoka.lg.jp/bunka/bunka/tact/index.html)
## 大宝馆:白墙与红色圆顶

大宝馆的白墙与红色圆顶(©japan-travel.guide)
大宝馆建于1915年,最初为纪念大正天皇即位而建。 它后来曾作为市立图书馆使用,如今介绍与鹤冈有关的人物及其事迹。
看外观时,可以留意圆顶、入口与窗户的比例。 走进馆内,再从高山樗牛、松森胤保等人物的资料,认识这座城市的文化生活。 松森胤保涉猎绘画、自然观察等多个领域,也让人物展览超出单一的文学主题。
[大宝馆:建筑沿革与参观信息www.chido.jp](https://www.chido.jp/taihokan/index.html)
## 庄内神社与旧城址

庄内神社入口的石鸟居与祈愿装饰
庄内神社位于鹤冈城本丸旧址,供奉与酒井家有关的四位人物。 神社、护城河遗迹与公园空间放在一起看,可以更容易理解城郭原来的位置。
这里也让人看到,城郭退出军事用途以后,旧址怎样转变为今天的纪念与日常活动空间。 想参观宝物殿,应另外确认开放日与展览内容。
[鹤冈市:酒井家与庄内历史www.city.tsuruoka.lg.jp](https://www.city.tsuruoka.lg.jp/static/sakai400th/history.html)
## 鹤冈天主教堂

鹤冈天主教堂的白墙与红色尖塔(©japan-travel.guide)
鹤冈天主教堂建于1903年,天主堂是日本国家重要文化财。 红色尖塔与白色墙面,在市区街道上很醒目。
堂内的“黑色圣母像”是一尊雕像;另一项特别的装饰,是将绘画夹在玻璃之间的窗画。 欣赏时可以分别观察它们的材质与光线效果。
这里仍是举行宗教活动的教堂。 参观时间应配合礼拜与现场安排,在允许开放的空间里安静停留。
[鹤冈市官方观光信息www.tsuruokakanko.com](https://www.tsuruokakanko.com/)
## 从车站到步行核心区
- ✈️ 乘飞机:可查询羽田至庄内机场的航班,再接续前往鹤冈的机场巴士。
- 🚃 乘铁路:从东京方向可比较经新潟转乘羽越本线特急等路线,按当日时刻安排行程。
- 🚌 抵达鹤冈站后:可乘市区公交到市役所、博物馆一带,再步行游览周边。
若只想走得从容一些,可以把致道博物馆作为上午的重点,下午在公园、大宝馆与教堂之间选择。 文化会馆则按演出或公共空间的开放安排加入,给每一处都留一点真正停下来的时间。
### 小江户川越经典一日游
URL: https://www.sakashita.page/kawagoe-day-trip/
Last updated: 2026-10-23T23:19:59.000Z
川越的一日散步,可以从两种空间开始:城郭留下的御殿,与旧商业街上的蔵造建筑。 它们之间还有寺院、美术馆与零食小巷,适合按自己的兴趣串起来。 不必把所有地点都走到,先选一两个想认真看的部分更容易安排。
## 先认准抵达的车站
从池袋方向可乘东武东上线到川越,从大宫方向可乘JR川越线,从西武新宿方向则可到本川越。 川越站与本川越站不是同一个站,集合与返程时先说清名称。

川越街头的小江户循环巴士
步行与循环巴士可以互相配合,不过不同运营方的路线与票券范围各有条件。 选择票券时,把当天想去的地点与运营方的路线图放在一起比较即可。
## 城郭一侧:本丸御殿与三芳野神社

川越城本丸御殿的正面入口
川越城最初筑于1457年,现存本丸御殿属于1848年营建后保存下来的部分。 它与高大的天守不同,值得看的是入口、室内空间与行政居住功能。 背景可参考[观光协会的御殿介绍](https://koedo.or.jp/spot%5F007/)。

三芳野神社鲜艳的朱红色社殿
三芳野神社与城郭历史关系密切,现有社殿又经历历次重建和修理。 其中,1624年的重建留下了江户幕府时期的历史线索。 更详细的沿革见[川越市的文化财记录](https://www.city.kawagoe.saitama.jp/kurashi/bunka/1003787/1003798/1003884/1003885/1003894.html)。
## 美术馆与博物馆,按兴趣选一个

川越市美术馆的现代建筑

川越市立博物馆前的石碑
美术馆与博物馆靠近御殿区域,适合先看当前展览,再决定要不要安排完整的入馆时间。 馆藏与当期展览各有重点,喜欢绘画或城市历史的读者,可以分别查看展览主题。 准备参观时,以川越市发布的当期展览与休馆通知为准。
## 莲馨寺:从城郭走回街道

莲馨寺的参道与堂宇
莲馨寺位于通往蔵造街区的路线一带。 寺前受到亲近的“おびんずる様”是宾头卢尊者像,触摸祈愿是这里流传的信仰习俗。
[寺方官网](https://renkeiji.jp/)介绍了这处寺院与城市生活的联系。 在这里稍作停留,再进入商业街,步行的节奏也会自然改变。
## 蔵造街区与时之钟
蔵造建筑让川越保留了很有辨识度的街景,但这些仍是商店、道路与生活空间。 看建筑时,也留意车辆与过街位置,不在车道中停留拍照。
时之钟是街区里的醒目标志,今天看到的是1893年大火后重建的钟楼。 它与周边街道一起看,比只记一个钟响时间更能理解这处地标的位置。 想听钟声,可以在当日行程里留出等候,而不必为赶上某一声不断折返。
## 祭典、会馆与菓子屋横丁
川越祭的山车行事,作为“山、鉾、屋台行事”的组成部分,于2016年列入联合国教科文组织非物质文化遗产名录。 这并不意味着整座城市或所有祭典活动都以同一种方式获得认定。 平日想了解山车与祭典,可以另查会馆的当期开馆安排。
最后去菓子屋横丁,挑几样真正想吃的零食,再找地方坐下来。 店数与菜单会变,旅行不必围绕一份必须买齐的清单。 一日路线从旧建筑走到今天的店铺,川越的过去与现在也就接了起来。
### 草津温泉之旅
URL: https://www.sakashita.page/kusatsu-onsen/
Last updated: 2026-10-23T23:09:59.000Z
草津温泉的中心,是汤畑周围不断流动的水与人。 白天可以看木槽、石面与街道的关系,天暗以后,灯光又让蒸汽有了不同的层次。 从汤畑出发,走向附近浴场与公园,可以把看景、散步与泡汤连成一天。
## 汤畑:温泉的景观,也是引汤设施

夜色初起时草津汤畑旁的拍照看板

草津汤畑的木槽与袅袅温泉蒸汽
汤畑的木槽既与温泉降温有关,也用于采集汤之花。 它不是装饰性的水池,周围也有高温泉水与不可进入的区域。 沿开放通道看水流与蒸汽,就能认识草津如何把泉源接进温泉街的日常。

夜幕下的草津温泉“汤畑”
入夜后可以再回来走一圈,观察同一组木槽与石面在灯下的变化。
## 热乃汤:看汤揉与歌声配合

热乃汤的木板汤揉表演
汤揉与给高温温泉降温的传统有关,今天热乃汤的演出则把木板动作、歌声与节奏呈现在观众面前。 各浴场的加水、加温、循环与消毒方式,应分别看设施的温泉标示。
[草津町的汤揉介绍](https://www.town.kusatsu.gunma.jp/www/contents/1485319039936/index.html)提供官方入口。 看演出与泡汤是不同活动,安排时分别确认票券与时间。
## 御座之汤:在汤畑旁选一处浴场

草津御座之汤的木造外观
御座之汤位于汤畑附近,今天的建筑再现了旧时共同浴场的意象。 设施使用汤畑与万代两个泉源,泡汤前可以从各浴槽的说明认识差别。
开放、入浴与休息空间的安排,见[御座之汤官网](https://onsen-kusatsu.com/gozanoyu/)。 想在散步中加入一次泡汤,这里与温泉街的位置关系比较容易安排,但也不必连续进多家浴场才算来过草津。
## 西之河原:公园散步与露天浴场分开看

西之河原公园内的温泉石景
从汤畑往西之河原方向走,街道逐渐接到更开阔的公园空间。 泉水与溪流形成的景观,可以沿步道慢慢看;普通公园开放区域与收费露天浴场,是不同的使用范围。

草津穴守稻荷神社的红色鸟居参道

西之河原公园的入口与休息亭
想泡露天温泉,可查[草津三汤的设施信息](https://onsen-kusatsu.com/)。 特别活动、混浴安排、清洗设施与所需用品,都应按当期说明准备,不沿用旧文章的某个星期与时间段。
## 温泉之门:路口的另一处停留

夜间灯光映照的草津温泉之门足汤
温泉之门于2023年建成,位于进入草津温泉街的道路一带。 足汤与交通设施让这里成为另一处可以停留的地点,但它不在汤畑旁,步行或自驾都要分别考虑路线。
一晚住在草津,可以把白天看景、傍晚泡汤与夜间散步分开。 不必用排行榜决定这里应带来怎样的感受,水声、蒸汽与慢下来的脚步,已经足够构成一次温泉旅行。
### 轻井泽散步:旧街、林间教堂与温泉
URL: https://www.sakashita.page/karuizawa-day-trip/
Last updated: 2026-10-23T23:00:00.000Z
轻井泽的景点分布在不同街区,一天最值得先做的事,是选定想怎么过。 旧轻井泽适合逛街和看教堂,中轻井泽的星野地区适合林间散步与泡汤,车站南侧则是购物广场。
这篇沿着旅行中看到的地方展开,再给出一天的取舍方式。 照片记录的是2023年的到访,部分场馆和交通安排后来已有变化。
## 林间小镇与度假文化

轻井泽林间的木造教堂
轻井泽位于长野县东部的高原。 明治时代,传教士亚历山大·克罗夫特·萧等人推动当地避暑文化的发展,后来形成别墅、教堂和商店交织的街景。
今天散步时,仍能看到木造建筑与树林相互衬托的景色。 早晚气温变化明显,即使天气晴好,也适合带一件外套。
## 轻井泽站:北口散步,南口购物

轻井泽站的站房与北口广场
从东京可以乘北陆新干线前往轻井泽。 出站后先看方向:北口通向旧轻井泽一带,南口邻近王子购物广场。

轻井泽站旧站舍纪念馆
旧站舍纪念馆就在车站附近。 想慢慢走旧轻井泽,可以先寄放大件行李,再按天气选择步行、巴士或自行车。

2023年到访时车站前自行车店的租赁告示
自行车的车型、费用和行李寄放条件,以各店当前说明为准。 冬季或路面结冰时,不适合照搬夏天的骑行安排。
## 轻井泽新美术馆

轻井泽新美术馆通透的玻璃外墙
从车站北口向旧轻井泽方向走,可以把新美术馆安排为中途一站。 馆内有展厅、画廊与商店,各空间的入场方式不同,先看当前展览再决定停留时间。

白桦林间相互支撑的心形艺术装置
馆内的心形装置出自让-米歇尔·奥托尼耶,玻璃礼拜堂由隈研吾设计。 白桦与苔地延伸到玻璃边缘,室内外的界线也变得柔和。 参观礼拜堂需要按馆方的指定方式安排,婚礼使用时也有不同限制。

轻井泽新美术馆一层的展示与休息空间
[轻井泽新美术馆:展览、设施与参观knam.jp](https://knam.jp/)
## 旧轻井泽银座:从店铺走向树林

旧轻井泽银座的咖啡与冰淇淋店

旧轻井泽银座的手工火腿香肠店

旧轻井泽银座街头的杂货小店
旧轻井泽银座有老店,也有咖啡、甜点、火腿香肠和杂货等店铺。 把午餐与购物留在这一带,就不用为了每一家店反复往返。
沿街的观光会馆可查询周边交通与活动。 继续走向树林,便能来到萧纪念礼拜堂一带。
[轻井泽观光协会karuizawa-kankokyokai.jp](https://karuizawa-kankokyokai.jp/)
### 萧纪念礼拜堂

林木环绕的轻井泽萧纪念礼拜堂
现有礼拜堂的原形建于1895年,之后经过增改建与修复保存至今。 它属于日本圣公会,木造建筑与周围的树林共同构成了安静的空间。
这里仍举行礼拜,参观时请尊重当天的宗教活动。 [教会的官方介绍](https://nskk-chubu.org/church/16shaw/)提供历史和参观说明。
### 圣保罗天主教堂

圣保罗天主教会的尖顶与钟楼
圣保罗天主教堂于1935年设立,三角形屋顶与钟楼让它很容易辨认。 它与萧纪念礼拜堂分属不同教会,也是另一处适合从外部细看建筑的地点。
\*\*顺路延伸:\*\*旧三笠饭店已在2025年10月重新开放。 如果对近代度假建筑特别感兴趣,可以另外留出交通与参观时间,查看[轻井泽町的开放指南](https://www.town.karuizawa.lg.jp/page/24270.html)。
## 千泷温泉:为泡汤另留时间

轻井泽千泷温泉的木质入口
千泷温泉位于中轻井泽以北一带,有林间浴场等设施。 它和车站、旧轻井泽的步行街分属不同区域,需要另外安排交通。
当天若以泡汤为重点,就减少商店街的停靠点。 开放时间、交通和最晚受理可查[千泷温泉官方网站](https://www.princehotels.co.jp/sengataki/hours/)。
## 高原教会:白天的木屋与夜间烛光

高原教会庭院夜间点亮的烛光
高原教会位于星野地区,三角形屋顶与高大的树林相衬。 季节性烛光活动让夜晚有另一种气氛,但活动日、入场与预约要求需按当年公告确认。

白天的轻井泽高原教会
白天看教堂,可以留意木材、屋顶与周围树木的关系。 婚礼和礼拜进行时,开放区域会受到影响,参观以现场指引为准。
[轻井泽高原教会www.blestoncourt.com](https://www.blestoncourt.com/wedding/ceremony/church%5Fkaruizawa/)
## 云场池:沿水散步

轻井泽散步途中看到的水草、小溪与树林
云场池靠近旧轻井泽一带,适合与北口方向的散步结合。 池畔能看树木与倒影,春天的新绿、秋天的色彩各有不同。
它与星野地区之间还有一段交通。 如果一天内两边都去,应先查巴士班次和步行距离,不要把不同区域的散步都算成几分钟。
## 星野温泉蜻蜓之汤

星野温泉蜻蜓之汤的建筑与草坪
蜻蜓之汤是可以日归利用的温泉设施,不必入住星野地区的酒店。 泡汤前后,还可以在附近餐饮设施休息,再沿汤川散步。

星野度假区树林间流淌的汤川
树林和流水让这一带适合放慢节奏。 浴场的票价、开放与使用规定,请查看[蜻蜓之汤官方网站](https://www.hoshino-area.jp/tombo-no-yu/);指定饮泉设施有专门说明,普通浴槽的水不用于饮用。
## 车站南口的购物广场
轻井泽王子购物广场就在车站南侧,适合放在返程前。 商店、餐饮和草地空间比较分散,认真购物也需要时间,别把它只当成最后半小时的附加项目。
[轻井泽王子购物广场www.karuizawa-psp.jp](https://www.karuizawa-psp.jp/ch)
## 一天选哪条路线
- 🚶以街区为主:轻井泽站北口、新美术馆、旧轻井泽银座与教堂,再按时间加入云场池。
- 🚌以休息为主:前往中轻井泽或星野地区,在教会、树林和一处温泉之间慢慢安排。
- 🚆返程前:如果还有余力,再到车站南口吃饭或购物。
如果想同时看夜间活动、泡汤和认真逛店,住一晚会更轻松。 冬季日落早,路面可能结冰,行程也应比夏季留出更多余量。
### 青森中野红叶山的秋日风景
URL: https://www.sakashita.page/nakano-momiji-autumn-leaves/
Last updated: 2026-10-23T22:49:59.000Z
中野红叶山位于青森县黑石市。 溪流、小桥、神社与山坡上的树木聚在不大的范围里,秋天走进去,颜色会随着脚步一层层展开。 从河边走到山上,同一片秋色会呈现出不同的层次。
## 从鸟居走到溪边

黑石中野神社入口的石鸟居

中野川两岸层层铺开的红叶
进入中野神社一带,先听见溪水,再看到岸边的红叶。 桥头很容易让人停下来拍照,不过路幅有限,看一会儿以后也给其他行人留出位置。
这里的植枫历史,一般追溯到1802年弘前藩主津轻宁亲从京都引入枫苗的记录。 [黑石观光协会的介绍](https://kuroishi.or.jp/sightseeing/nakanomomijiyama)同时说明了神社、地方信仰与树木保护。

中野红叶山溪边的木芥子人偶装饰

铺满落叶的中野红叶山林地
## 神社与山坡的颜色

中野神社朱红鸟居前的石灯笼
从溪边继续向神社走,可以看到大杉与枫树在同一片林地里形成不同的高度。 神社与津轻三不动信仰有关,地方传说也为这片山林增添了故事。

游客漫步中野红叶山的林间小径
再沿开放的小径往观枫台方向走,颜色与刚才的溪岸又不一样。 有的叶片仍带黄,有的已经落在路上,林间光线也更零碎。 这样的变化,比追求整座山同时变成一种鲜红色更耐看。

中野红叶山交织的红黄秋叶

中野川上方鲜红的枫叶
## 不动桥与水面

秋叶环绕的不动桥与溪水

从红色桥栏旁欣赏中野山层叠秋色
桥、瀑布与溪流让红叶有了前后层次。 照片里看起来密集的颜色,到了现场仍需要在不同位置慢慢找角度。 不必为了同一张照片越过围栏或踩进树木根部,沿开放路径已经能看到许多变化。
## 花期之外,也要看活动日期
中野红叶山通常在10月下旬至11月上旬受到关注,实际转色取决于当年的天气。 夜间点灯另有会期与时间,不能把旧文章中的某一组日期当成每年固定安排。 观光协会的同一页面会更新当年的活动与交通,可在出发前再确认。
## 从黑石接上最后一段交通
到黑石以后,还需要巴士、出租车或自驾前往。 巴士站名与不同线路的下车位置不完全相同,确认去程时也一并查返程,尤其是准备看点灯的人。 若经弘前换乘弘南铁道,也应按实际运行情况重新查路线。
山路与桥边在落叶、雨水或寒冷天气下容易变滑。 穿便于行走的鞋,按当天状态决定走到哪里即可。 看完溪边,再看一段山坡,就已经能记住中野红叶山的秋色。
### 秋田港与男鹿半岛:寒风山、生剥鬼与海边风景
URL: https://www.sakashita.page/akita-city-guide/
Last updated: 2026-10-23T22:39:59.000Z
从秋田港到男鹿半岛,风景会从港口的玻璃高塔,变成开阔的草坡,再走进生剥鬼的民俗世界。 这篇把沿途的几个地点放在一起,适合用来理解秋田市与男鹿半岛之间的旅行尺度。 它们并不都在秋田市中心,安排交通时应把市内移动和半岛上的移动分开考虑。
## 秋田港:从高处认识海岸与城市

秋田港高塔的夜间灯光

沿途见到的风力发电机

道之站秋田港的设施与导览信息
秋田港的“道之站”不只是开车时休息的地方,也把物产、餐饮和观景空间放在了一起。 其中最醒目的地标是SELION塔。 展望室位于地上约100米,可以环视港口、秋田市区与日本海;天气良好时,还能看向男鹿半岛和鸟海山一带。
对第一次来秋田的人来说,先从高处看一遍海岸与市街,再回到地面散步,会比较容易建立方向感。 有关展望设施、物产与餐饮,可查看[秋田市观光信息](https://www.akita-yulala.jp/selection/5000014107)。
## 寒风山:草坡下的火山地形

寒风山山顶的标志与远处海景

被芒草覆盖的寒风山草坡

在寒风山上空飞行的滑翔伞
寒风山位于男鹿半岛东部,海拔355米。 山的高度不算突出,草坡和较少遮挡的视野却让地形显得开阔。 它属于男鹿半岛与大潟地质公园的一部分,火口、熔岩与周围地貌,是理解这里比单纯登高看景更有意思的线索。
关于火山年代,应把地质资料与后来的传说分开。 例如有些介绍会提到1810年喷发,但[产业技术综合研究所的火山资料](https://gbank.gsj.jp/volcano/Quat%5FVol/volcano%5Fdata/D34.html)指出,这一记载缺少具体喷发与灾害状况的依据。 因此不宜把这个年份当成已经确定的最后喷发记录。

从寒风山眺望海岸、湖泊与平原

寒风山展望设施附近的旅行记录
在山顶附近,可以慢慢辨认海岸、平原与周围山地的位置。 草坡上的风也很直接,衣物、帽子和手里的物品都需要留意。 滑翔伞活动则取决于天气和运营条件,不能因为当天看见有人飞行,就把它视为随时能够参加的项目。
## 生剥鬼馆:面具与地方生活

以男鹿石砌成外墙的生剥鬼馆

馆外的红叶与水景
生剥鬼馆介绍男鹿地区的生剥鬼习俗、面具与装束。 看过不同地区的造型后,会发现“生剥鬼”并不是只有一张固定的脸。 面具的表情、衣着与讲述方式,都与地方社会的传承有关。

馆内的红面生剥鬼展示

身披蓑衣的生剥鬼人偶
在当地习俗里,生剥鬼是按年节来到家中的来访神。 夸张的声音与动作虽然容易让人先想到“吓小孩”,背后也有迎接新年、劝诫懒惰、祈求家人平安的意味。 参观时,连同迎接者怎样应对、家人怎样款待一起看,才比较接近这个仪式的完整面貌。
## 真山传承馆:把仪式放回民居里看
邻近的男鹿真山传承馆通过实演,呈现当地家庭迎接生剥鬼的情景。 看点不只在面具走进房间的那一刻,也在主人与来访者的对话、劝诫和款待过程。 展演让游客接近习俗的一部分,但各地区实际的年节行事仍有各自的安排,不宜把一次实演当成所有地方完全相同的规则。
馆舍与展演介绍可查看[男鹿半岛与大潟地质公园的说明](https://www.oga-ogata-geo.jp/about/geosites/central/namahage),参观项目和时间则以[生剥鬼馆](https://namahage.co.jp/namahagekan/)的通知为准。
## 交通怎样串起来
- 🚆 秋田港可从秋田市区安排交通前往,先确认到SELION周边的巴士或其他接驳。
- 🚗 寒风山、生剥鬼馆与真山传承馆位于男鹿半岛,地点之间还有距离,适合单独核对车程、停车与当地接驳。
如果利用JR男鹿线前往男鹿站,还要继续安排车站到各设施的一段。 不自驾时,先把当天能用的当地交通查清,再决定看几个地点,会比把所有景点塞进同一条时间表更踏实。 男鹿半岛的观光与交通信息可从[男鹿观光导航](https://oganavi.com/)查询。
### 深秋的角馆:武家宅邸与红叶
URL: https://www.sakashita.page/kakunodate-in-autumn/
Last updated: 2026-10-23T22:29:59.000Z
角馆的秋天,适合沿着院墙与树木慢慢走。 武家屋敷街、工艺馆与小店分布在步行路线里,红叶又让庭院与街道多了一层颜色。 这篇围绕以前留下的照片,认识几处不同性质的空间。
## 街道先于单独一座宅邸
角馆在17世纪初形成城下町,武家与町人居住区之间保留了防火地等空间安排。 今天看宅邸时,也值得留意街宽、围墙与院门怎样相接,而不只问哪一间“最有名”。

穿过角馆秋日街道的人力车

角馆武家屋敷庭园的红叶与石景
人力车可以作为另一种认识街道的方式,但路线、讲解与费用各有安排。 是否乘坐按兴趣决定,也给步行看庭院留出时间。 走进宅邸前,先看入口标示的公开范围,让参观与住家的日常生活各自保有空间。
## 桦细工传承馆:山樱树皮的纹理

角馆桦细工传承馆的建筑

桦细工传承馆的门楼
桦细工使用山樱树皮,不是用桦树皮制作。 仙北市的[工艺记录](https://www.city.semboku.akita.jp/sightseeing/bussan/02%5F12.html)介绍了藤村彦六习得、传播技艺的过程,以及这门手艺在角馆的发展。
观察作品时,可以看纹理如何拼接,树皮与木胎怎样组成盒、茶筒或其他器物。 馆内展览与制作演示各有安排,出发前可以查当日的活动内容。
## 岩桥家:看屋顶与庭院的位置

岩桥家宅的木门与秋色

岩桥家宅的木造建筑与庭院,开放范围依现场安排
岩桥家让人可以从建筑与庭院的关系,认识武家宅邸的一种尺度。 屋顶、入口与围墙值得一起看,古树则把院落的高度继续往上延伸。 历史住宅不是可以任意进入的空屋,参观以开放区域为准。
## 午餐与沿街小店

角馆街头由传统房屋改建的店铺

角馆街边售卖冰淇淋的小店

古泉洞餐馆的木造门面与暖帘
角馆有利用传统建筑经营的餐馆与商店,沿路可以找稻庭乌冬面、米饭料理或甜点。 看到感兴趣的一家,再看看当天的菜单,给午餐或下午茶留一点余量。
## 石黑家:室内空间与公开安排

红叶掩映的石黑家宅入口

石黑家宅内的木构与扇形装饰
石黑家是认识角馆武家住宅的重要地点,公开部分与家族的私人生活空间长期相邻。 看导览介绍时,可以留意屋内怎样分隔与使用,以及建筑细节如何表现宅邸的身份。
观光协会已公布,石黑家于2026年11月30日结束一般公开。 之后的行程不能继续照旧安排入内,详见[公开休止通知](https://tazawako-kakunodate.com/news/19439/)。 计划之后到访的读者,可以改查其他宅邸与展馆的开放安排。
## 平福纪念美术馆

平福纪念美术馆的入口廊柱

美术馆附近秋叶间的纪念石碑

平福纪念美术馆周边的秋日建筑与林木
美术馆以角馆出身的平福穗庵、百穗父子得名,也介绍与当地有关的其他画家。 建筑由大江宏设计,背景见[仙北市的设馆记录](https://www.city.semboku.akita.jp/sightseeing/hirafuku/keii.html)。 馆外的柱廊与林木适合慢慢看,馆内则按当前展览安排停留。
从角馆站走到武家屋敷街需要一段路,返程也给休息和取行李留出余量。 一条街、几座院子与一处工艺馆,已经可以把角馆的秋天看得很具体。
### 山形酒田:沿着《阿信》的足迹逛港城
URL: https://www.sakashita.page/sakata-and-oshin/
Last updated: 2026-10-23T22:19:59.000Z

山居仓库的观光看板与人物造型拍照板
提到酒田,许多人会先想起1983年开始播出的NHK连续剧《阿信》。 这座山形县的港口城市是剧中的生活舞台和拍摄地之一,山居仓库尤其容易唤起对作品的记忆。
酒田的吸引力也藏在日常街景里。 旧商家的木廊、仓库旁的榉树、安静的美术馆,以及一碗热腾腾的拉面,可以串成很充实的一天。
## 本间家旧本邸:从一座宅院看商都

本间家旧本邸沿庭院展开的木廊

本间家旧本邸的白墙与深色木板围墙
本间家是推动酒田发展的商人家族。 1768年,第三代家主本间光丘为庄内藩主酒井家建造了这座迎接幕府巡见使的宿舍,后来受赐作为本间家的宅邸。

本间家旧本邸入口与庭院中的老松
这座宅院把武家建筑与商家生活空间结合在一起。 参观时可以留意房间之间的关系,以及长廊怎样把视线引向庭院。 门前的老松也很醒目,枝干伸展在屋檐旁,给宅院添了几分厚重感。
[本间家旧本邸参观信息sakata-kankou.com](https://sakata-kankou.com/spot/30133)
### 马路对面的别馆

本间家旧本邸别馆内的老式秤具

本间家旧本邸别馆的传统店面
旧本邸别馆保留了店铺的面貌,展示账房用品、度量衡器具、灯具和厨房用具等。 主宅看的是空间与庭院,别馆则更接近日常经营。 把两处连起来看,更容易理解本间家作为商人家族的一面。
## 山居仓库:榉树、木墙与河道
山居仓库建于1893年,曾承担庄内米的储存与集散。 这里利用榉树遮挡西晒,并通过屋顶等建筑构造调节仓储环境。 “庄内”指山形县面向日本海的地区。

山居仓库木墙旁的榉树步道
沿仓库旁的步道走,树影和深色木墙形成连续的画面。 秋天的树叶与仓库外墙很相衬,河岸一侧又能看见另一种开阔的景色。

山居仓库旁河道上的人行桥

沿河排列的山居仓库木造建筑
山居仓库于2021年成为日本国家史迹,2022年9月结束了米仓的实际运营。 如今来这里,主要是看仓库外观、榉树步道及介绍历史的展示。
\*\*参观更新:\*\*原“酒田梦之俱乐部”物产馆已于2025年2月底结束在仓库内的营业,庄内米历史资料馆也处于闭馆状态。 山居仓库的信息中心提供历史介绍,具体开放区域请查看[酒田官方参观指南](https://sakata-kankou.com/spot/30159/)。
## 饭森山:把时间留给美术馆
市中心的旧宅与仓库可以串联散步或骑行。 酒田市美术馆和土门拳的摄影美术馆位于饭森山一带,适合另安排半天,再乘巴士或出租车往返。
### 酒田市美术馆

酒田市美术馆的外墙与入口前草地
酒田市美术馆收藏森田茂、高桥刚、国领经郎等艺术家的作品。 实际展出的作品随展览更换,出发前看看展览主题,比较容易决定停留时间。
[酒田市美术馆官方网站www.sakata-art-museum.jp](https://www.sakata-art-museum.jp/)
### 土门拳摄影美术馆
这里原称土门拳纪念馆,2025年4月起使用“土门拳摄影美术馆”的名称。 土门拳出生于酒田,以《古寺巡礼》《广岛》等作品闻名。 馆内收藏约七万件作品,通过轮换展览逐步介绍他的摄影世界。

土门拳纪念馆内曾拍摄的摄影作品展览
建筑由谷口吉生设计,1983年开馆。 馆中的庭院、雕塑和水面,也值得在看展之后慢慢观察。

土门拳纪念馆窗边的雕塑与庭院景色
中庭雕塑出自野口勇,造园与部分艺术作品由敕使河原宏创作。 从展厅移步到窗边,室内的照片与室外的空间接续起来,参观节奏也随之慢下来。

土门拳纪念馆的石材外墙与草坪
展厅拍摄及作品公开规则可能随展览而异,参观时请确认现场说明。 开放日期和当期展览可从[酒田市的美术馆介绍](https://www.city.sakata.lg.jp/mobile/bunka/geijyutsu/bunkashisetsu/dmonken%5Fkinenkan.html)查询。
## 酒田拉面:两家亲自尝过的店
这次旅行中吃过的店是“満月”和“花鳥風月”。 两家都是体验酒田拉面的选择;想连续吃两家,可以分到不同的用餐时段,给街区散步留点余地。
“満月”的店在东中之口町,“花鳥風月”酒田本店在东町,两处位置不同。 选择店铺时,把当天的路线与交通一起考虑,营业时间和排队情况则出发前再确认。
## 交通与行程取舍
- 🚆铁路:从东京乘上越新干线到新潟,再换乘特急Inaho前往酒田。
- ✈️航空:飞往庄内机场,再转乘机场巴士前往市区。
- 🚲市内:天气合适时,可在酒田站前MIRAINI等指定地点借用观光自行车。
[酒田免费观光自行车的借还规定sakata-kankou.com](https://sakata-kankou.com/spot/41365)
观光自行车数量有限,通常冬季暂停提供,需要当天归还。 如果一天内加入两座美术馆,建议上午参观本间家和山居仓库,下午集中在饭森山,晚餐再回市中心吃拉面。 只想轻松散步的话,旧宅、仓库和一顿饭已经足够。
### 大阪梅田蓝天大厦与空中庭园
URL: https://www.sakashita.page/umeda-sky-building/
Last updated: 2026-10-23T22:09:59.000Z
在梅田看夜景,蓝天大厦的空中庭园是一个可以把城市与河流一起放进视野的地方。 这次记录从高处的观景台开始,也保留了大阪站北侧广场和淀川一带的照片。 先从地面认识车站与街区,再上楼看它们怎样连接起来,是这段散步的乐趣。
## 两座塔楼之间的空中庭园

从梅田蓝天大厦高处俯瞰圆形中庭与空中扶梯
按照馆方的[建筑资料](https://www.skybldg.co.jp/study/),梅田蓝天大厦于1993年建成,由建筑师原广司设计。 两座塔楼在高处连接,环形开口与跨越其间的扶梯,让建筑本身也成了参观的一部分。 这里的“空中庭园”主要指观景空间,并不是一座铺满植物的高空公园。

空中庭园展望台的参观纪念标识
观景台的入口路线与普通办公楼电梯不同,应按空中庭园的专用指引上楼。 经过电梯与扶梯后,先到售票、入口所在的楼层,再继续前往室内展望空间和屋顶步道。 具体进场方式、门票与当日开放安排,可查[官方参观信息](https://www.skybldg.co.jp/observatory/information/)。
## 在室内坐一会儿

cafe SKY40的咖啡吧台
室内观景层可以一边看窗外,一边坐下来喝点东西。 旅行时看到的咖啡吧台,也提供了在上下楼之间慢一点的理由。 菜单与饮品价格会变化,可以先看看当日供应,再决定是否停留。

空中庭园的室内观景台
室内与屋顶的看法并不相同。 窗边适合观察近处的建筑和铁路,走到室外,则能更直接地感受到风与城市的开阔。 想看日落到夜景的变化,就把入场时间和吃饭安排接起来,留出完整的一段停留。
## 夜色中的屋顶步道

从空中庭园展望台俯瞰大阪夜景
夜幕降下来以后,高楼的灯、移动的列车和道路上的车流,给城市添上了与白天不同的轮廓。 近处是大阪站周边的密集建筑,视线再往外延伸,则能找到淀川的方向。 不必急着辨认每一座楼,先看道路、河流和街区的明暗关系,也很有意思。

空中庭园观景步道上发光的星球图案
屋顶步道的发光地面与城市灯光叠在一起,是这次夜景照片里很鲜明的细节。 室外有风,较冷的季节要为这一段停留准备合适的衣物。 遇到天气影响或临时调整时,屋顶能否进入仍按当天馆方引导,不宜把过去一次参观时的说明当成长期开放承诺。

从空中庭园望向淀川两岸
## 大阪站北侧的广场与公共艺术

GRAND FRONT大阪的广场
从大阪站前往蓝天大厦一带,可以顺路看看GRAND FRONT大阪和梅北广场。 这里既有商店、餐饮和办公空间,也有适合短暂停下来的户外区域。 若晚上还准备上观景台,就可以把晚餐或咖啡安排在同一片街区。
广场水景中的绿色熊雕塑,是Fabrice Hyber的作品“TED HYBER”。 [设施方的作品介绍](https://www.grandfront-osaka.jp/TEDHYBER/)说明,它于2018年设置,绿色与艺术家持续关注的自然和人之间的关系相连。 知道作品的名字和来历以后,再遇到它随季节变化的装饰,也会更容易留意其中的趣味。

夜晚亮灯的梅北SHIP HALL
亮灯后的SHIP HALL和广场水面,让地面的夜间散步也有自己的节奏。 这一带仍会因活动、施工或街区更新改变步行路线,实际前往时可以结合站内指引与[蓝天大厦官网](https://www.skybldg.co.jp/)的交通说明。
## 淀川与特别活动

淀川和十三大桥
从高处看过淀川,再在地图上找一找十三大桥,城市的方位会更具体。 若是为淀川烟花大会或其他特别活动而来,观景台可能采用与普通日不同的票券和入场时段。 这类场次应单独确认官方公告,不能直接沿用旧照片记录里的日期或预约办法。
普通夜晚也值得停留:在室内看列车往来,走到屋顶感受风,再回到亮着灯的广场。 把这几个空间接起来,就能同时看到梅田的建筑、街道与夜色。
### 秋日新潟:庭园、港口与信浓川的夕阳
URL: https://www.sakashita.page/niigata-city-in-autumn/
Last updated: 2026-10-23T22:00:00.000Z
一个秋日,我和朋友从东京乘上越新干线前往新潟。 到站已经过了11点,我们买好当时的观光循环巴士一日券,开始这趟市区散步。
出发前,我对新潟最直接的印象是稻米。 这一天看到的庭园、商家住宅与港口建筑,却把城市的另一面慢慢展开。 最后在信浓川边等到夕阳,成了这次旅行里难忘的片刻。
## 旧齐藤家别邸:从室内看庭园

从旧齐藤家别邸室内望向庭园

旧齐藤家别邸的入口与木造建筑
旧齐藤家别邸由豪商齐藤家的第四代齐藤喜十郎于1918年营建。 建筑与利用沙丘地形的回游式庭园相接,坐在室内,也能感到屋檐、树木与坡地之间的层次。
庭园于2015年被指定为日本国家名胜。 参观时可以先看建筑怎样朝向庭园,再沿开放路线观察石组与树木;拍到的景色,也会随所处位置变化。
现在仍有保存与维修安排,出发前可以查可参观的空间与当日公告。
[旧齐藤家别邸:庭园与开放信息saitouke.jp](https://saitouke.jp/)
## 旧小泽家住宅:商家生活的空间

旧小泽家住宅庭园的松树与鹤形装饰

旧小泽家住宅深色木板外墙
小泽家从江户时代后期起,在新潟经营商业,后来也涉足运输等领域。 保存下来的店铺、住宅、仓库与庭园,让人可以从房屋布局认识港口商家的生活。
别邸与商住合一的町家,使用方式各有不同。 两处连着看,会比只比较庭园大小多一些收获。
[新潟市:旧小泽家住宅www.city.niigata.lg.jp](https://www.city.niigata.lg.jp/chuo/shisetsu/yoka/bunka/kyuozawake.html)
## 午餐:偶然找到的大桥屋
从旧小泽家住宅出来,我们已经有些饿了。 查了附近的店,便到大桥屋吃午餐。
那天点的是店员推荐的“柳都膳”,包括芝麻豆腐、刺身、米饭、味噌汤与饭后甜点。 “柳都”是新潟的别称,店员的介绍,也让一道午餐与城市的名字连了起来。
我对那餐的印象不错。 若准备再去,还是应先确认当前的营业日期、是否需要预约和菜单内容。
## 港口一带的历史博物馆

采用旧市厅舍设计元素的新潟市历史博物馆本馆

新潟港停泊的大型渡轮

历史博物馆本馆屋顶的时钟
新潟市历史博物馆“みなとぴあ”把河流、港口与城市发展联系起来。 本馆采用第二代新潟市厅舍的设计元素,是为博物馆新建的建筑。 参观时,我们通过常设展里的模型与影像,认识水与当地生活的关系。
**当前参观安排:博物馆本馆因空调工程休馆。** 旧新潟税关厅舍与旧第四银行住吉町支店仍有开放安排,具体范围与复馆消息应看馆方公告。
[新潟市历史博物馆:开放与工事公告www.nchm.jp](https://www.nchm.jp/)
### 旧第四银行住吉町支店

旧第四银行住吉町支店的建筑
这座银行建筑,让港口散步多了近代金融业的一面。 看外观后,也可以在开放区域里留意办公空间、装饰与材料的使用。 建筑当前还承担部分博物馆办公等功能,应按入口指示参观。
### 旧新潟税关厅舍

旧新潟税关厅舍
旧税关厅舍建于1869年,与新潟开港后的税务管理有关。 它是日本国家重要文化财,也是在这一带认识开港史的重要实物。 把它与现代港口和博物馆本馆放在一起看,能看到城市不同时期的面貌。
## 白山公园与白山神社

白山公园池畔的秋叶与喷泉
白山公园于1873年成为日本早期都市公园之一,造园与当时的县令楠本正隆有关。 今天,这里仍是市区里可以休息、散步与看季节变化的地方。

白山神社悬挂的Hello Kitty主题绘马

白山神社龙形出水口旁的花手水
相邻的白山神社有不同的祈愿习俗。 我们看到的绘马与花手水,给这段散步留下了很鲜明的颜色。 装饰会随季节和活动变化,适合带着一点期待,看看当天的布置。
## 县政纪念馆:保存中的旧议事堂

树木掩映的新潟县政纪念馆

县政纪念馆前的参观说明与建筑旧照
县政纪念馆原为1883年建成的新潟县会议事堂,兼有日式与西式建筑元素。 馆方目前因耐震改修而休馆,公告的休馆期间至2028年3月31日。 施工中的可见范围应按现场安排,不能按过去的入馆经验直接走进去。
[新潟县:县政纪念馆保存与休馆安排www.pref.niigata.lg.jp](https://www.pref.niigata.lg.jp/sec/bunka/1196612167437.html)
## 在RYUTOPIA看信浓川的夕阳

新潟市民艺术文化会馆RYUTOPIA的玻璃大厅

从文化会馆高处眺望信浓川的夕阳
RYUTOPIA设有音乐厅、剧场与能乐堂,也有可供休息的公共空间。 我们在高处看到了夕阳,信浓川与市区的颜色渐渐变暖,这一天也在这里慢了下来。
6层展望休息厅与馆内其他空间,有时会因活动或包场限制使用。 若特别想在傍晚到访,先看公告,再安排回站交通。
[RYUTOPIA:公共空间与活动www.ryutopia.or.jp](https://www.ryutopia.or.jp/)
这条路线跨越几个区域,观光循环巴士与步行可以交替使用。 再次出发时,先按目前开放的设施缩小范围,留出午餐和看河景的时间,就能保留这趟旅行最舒服的部分。
### 初冬函馆的经典游览地
URL: https://www.sakashita.page/hakodate-in-early-winter/
Last updated: 2026-10-23T21:49:59.000Z
这次函馆散步安排在初冬。 我们从元町的坡道与旧建筑走起,午后到海边吃饭,再搭市电去五棱郭。 因为已经安排了之后前往大间的渡轮行程,这次没有上函馆山看夜景,留下的是白天的城市风景。
函馆位于北海道南部,冬季仍有低温、风雪与结冰,海边的风也会影响体感。 初冬旅行尤其要看当天预报,给户外步行与交通变化留些余量。
## 元町公园与八幡坂一带

元町公园
从函馆站一带搭市电,可以到末广町或十字街等电停,再按路线进入元町。 八幡坂是看海港与坡道的代表地点,不过它也是日常通车的道路。 拍照时留在允许行走的位置,不站到车道中央等待一张没有车的照片。

从函馆元町一带眺望海湾与市区
我们从坡道一带转向元町公园,在可以眺望港口的地方稍作休息。 再往旧函馆区公会堂走,海面、街道与建筑之间的层次就慢慢连了起来。
## 旧函馆区公会堂

蓝黄相间的旧函馆区公会堂
公会堂蓝黄相间的外观很醒目。 参观建筑内部以后,我们也体验了换装拍照,让这段建筑参观多了一点轻松的记忆。 原来记录中的入场与服装租赁费用属于当时的消费信息,今天再去,应重新查开放与服务安排。
建筑与当前参观信息,可从[函馆市的观光介绍](https://www.hakobura.jp/spots/512)查起。 换装需要另外留时间,不宜紧接着安排必须准点赶到的下一站。
## 函馆正教会

函馆正教会的白色外墙与绿色尖顶
离开公会堂后,我们继续走向元町的教会区域。 函馆正教会的白色墙面、绿色屋顶与十字架,是这一带很有辨识度的风景。 现存圣堂建于1916年,经历保存修理后继续使用,并不是一处停止宗教活动的建筑陈列馆。
[官方观光页面](https://www.hakobura.jp/spots/524)区分了内部参观、祈祷与冬季休止等安排。 能在外围看建筑,不等于任何日期都能进圣堂;到了现场也应尊重礼拜与摄影规定。
## 金森红砖仓库:午后停下来吃饭

金森红砖仓库的历史建筑群
之后我们来到金森红砖仓库一带,约在下午两点吃午餐。 这里由历史仓库转为商业设施,店铺、餐饮与海边步行可以接在一起。 这段行程不需要每间店都走到,选一家合适的地方坐下来,正好给前面的坡路散步留个休息。
元町与海湾区相距不算远,但仍有上下坡。 是否继续步行,或搭市电回十字街附近,可以按体力与天气决定。
## 五棱郭:从高处看形状,再走进公园

从五棱郭塔俯瞰星形城郭与护城河
我们搭市电到五棱郭公园前站,再步行去五棱郭塔。 电停与公园并不是同一个地点,换乘时别漏算这段步行。
从塔上看,星形轮廓与护城河的关系十分清楚。 之后再到公园里走,平面图上的形状就变成了脚下真实的道路与水面。 票价与营业通知请看[五棱郭塔官网](https://www.goryokaku-tower.co.jp/),照片中的季节与当前园景也未必相同。
## 市电与返程
一天里多次搭市电,可以比较单次车费与一日券,先确认通用范围,再决定是否购买。 机场巴士与市电是不同交通方式,票券不能想当然地通用。
有些电停位于道路中间,过街要走规定通道、看信号灯。 如果还准备看函馆山夜景,最好另留一段完整时间,按缆车、天气与返程安排单独规划。 这次没有看成的部分,就留给下一次函馆旅行。
### 秋天的弘前城
URL: https://www.sakashita.page/hirosaki-castle-in-autumn/
Last updated: 2026-10-23T21:39:59.000Z
弘前公园不只在樱花盛开时值得来。 秋雨过后,护城河、桥与林木之间的颜色变得安静,沿着水边走,注意力也会从热闹的花景移到一条小径、一段城墙和远处的树冠上。 这次留下的是秋天的散步照片。

弘前城公园护城河边的秋色
## 先看城郭,再看季节
弘前城建于1611年,后来长期作为津轻氏的居城。 最初的天守因雷火烧毁,现存天守则是在19世纪初另行营建的建筑。 弘前市的[保存修理记录](https://city.hirosaki.aomori.jp/jubun/index.html)将其造营年代记为1810年;不能把它与最初筑城的年份混成同一个时间。
这里属于日本现存十二天守之一,公园里还保存着城门、橹与护城河等遗构。 看城时,不只是走到天守前拍照,沿途的地形与门桥关系也能帮助理解城郭的层次。

弘前公园樱之小径的指示牌
## 秋天沿着水道慢慢走

秋日弘前公园内林木环绕的水道
秋日公园适合把散步速度放慢。 树木的颜色并不总在同一天到达顶点,雨水、光线与落叶又会改变同一条路的样子。 比起预设每一处都应是鲜红色,沿水道找几处自己喜欢的停留点,更容易看见这段季节。
春天有樱花,秋天有菊花与红叶相关活动,冬天还有雪灯笼等不同景象。 这些活动的日期与开放区域需要分别确认,不宜用一份旧票价表概括公园全年。 夏季的弘前睡魔祭主要在市区巡游,也不是城内日常散步的一部分;它的民俗来源不能简单解释为驱逐一个叫“魔睡王”的人物。

秋雨后红叶掩映的弘前公园朱红色桥
## 今天再去,要先看修缮信息
这组照片记录的是以前的景观。 弘前城正在进行石垣、天守移动与建筑保存修理,照片里的位置、通行路线和登楼条件都可能与现在不同。
弘前市公布,天守自2025年11月24日起暂停内部公开,相关保存工程计划延续至2032年度,日程仍可能调整。 这项限制针对天守内部,不等于整个公园关闭。 行前应查[弘前城石垣修理项目](https://www.city.hirosaki.aomori.jp/ishigaki/index.html),把能进入的区域与想看的景色分开确认。
## 怎样接上交通
乘铁路到弘前站后,可以比较巴士与步行到公园的方案。 从新青森或青森机场进入弘前时,也应按当天班次重新查换乘,不沿用旧文章里固定的飞行与车程。
秋季散步不必把公园、远郊与全部市区建筑排在同一天。 留出一段完整的白天,走累了就休息,再回头看一眼桥与水面,这样便足够记住弘前的另一面。
### 镰仓东庆寺之行
URL: https://www.sakashita.page/tokeiji-temple/
Last updated: 2026-10-23T21:29:59.000Z
东庆寺位于北镰仓,从车站步行即可到达。 它以“缘切寺”的历史为人熟悉,但走进这里,也能从禅寺、人物与文化财的角度认识它。 先读懂寺院的由来,再安静参拜,比只把它当作拍花的景点更接近这座寺的性格。
## 为什么被称为“缘切寺”?
根据[东庆寺的官方历史介绍](https://tokeiji.com/about/history),寺院创建于1285年,属于临济宗圆觉寺派。 过去,东庆寺以帮助寻求离缘的女性而著称,这段历史一直延续到明治时期的制度变迁。 “逃进寺里”并不适合被简化成一个脱离时代背景、立刻就能完成离婚的程序。
寺院后来经历了从尼寺到近代禅寺的变化,也与释宗演、铃木大拙等人物有关。 参观前读一读寺方的年表,可以看见这里并非只有一个“缘切”的故事。
## 看文化财,也看当下的寺院
观音像等文化财有各自的公开与参观安排,可以结合寺方的说明,了解作品与寺院的关系。 如果想专程观看某件作品,应先查寺方的介绍与公开日期。 境内并不是所有空间都以一般观光设施的方式开放。
## 参拜前要知道的事
东庆寺已公告境内禁止摄影,参观时应收起相机和手机,遵守现场说明。 参拜时间、宝物馆休馆与活动请查看[东庆寺官方网站](https://tokeiji.com/)。 少拍几张照片,也能把注意力留给脚下的石路、周围的树木和寺院的安静。
### 高知市内一日游:高知城、弘人市场与路面电车
URL: https://www.sakashita.page/kochi-by-tram/
Last updated: 2026-10-23T21:19:59.000Z
这次在高知市内,我们主要乘路面电车移动。 从城郭到市场,从小桥到祭典体验,再看一点龙马与美术馆,电车把不同主题连成了一天。
如果想照着走,最好先选几个重点。 我们到访时的开放情况与今天可能不同,尤其体验预约和美术馆展览,需要另查当期安排。
## 高知城:跟着导览看城郭

高知城的天守与石垣(©japan-travel.guide)
高知城由山内一丰于1601年开始营建,本丸与二之丸于1603年完成,三之丸则到1611年才整备完成。 1727年火灾后,城郭又逐步重建,今天的天守属于18世纪的再建建筑。
这里的天守与本丸御殿一同保存下来,可以把防守、居住与仪礼空间连着看。 园区与建筑内部的参观范围不同,想进天守和御殿时,应另看购票与最后入馆时间。
这次遇到志愿导览员,他带我们认识城郭,也介绍了园内的板垣退助像。 有讲解陪着看,石垣、人物像与建筑就不再只是孤立的拍照点。
[高知城:历史与参观信息kochipark.jp](https://kochipark.jp/kochijyo/)
## 弘人市场与带屋町

弘人市场入口的招财猫(©japan-travel.guide)
离开高知城,可以到弘人市场一带吃饭。 不同店铺共用热闹的用餐空间,适合各自挑喜欢的食物,再坐下来休息。

市场里排队购买鲣鱼料理的客人

明神丸的稻草火烤鲣鱼
用稻草火烤的鲣鱼,是市场里很醒目的料理。 我这次点了鲣鱼拉面,对鲜美的汤留下了很深印象。 餐后又沿带屋町商店街散步,找了一家糕点店吃甜品。
店铺、座位与营业安排会变化,想吃哪一家,可以到现场先看看菜单与排队情况。
[弘人市场:店铺与营业安排hirome.co.jp](https://hirome.co.jp/)
## 播磨桥:市中心的一小段水景

播磨桥的朱红色桥栏

绿柳旁的播磨桥水景
播磨桥位于市中心,也与纯信和阿马的爱情故事联系在一起。 如今看到的小桥与水景,属于经过整备的公园空间。 它的尺度不大,适合放在街区散步中短暂停留。
电车的“はりまや橋”站是换乘的重要节点。 查路线时,可以把站名、桥的位置与要去的商店街分别确认清楚。
## 在夜来祭资讯交流馆制作鸣子

播磨桥商店街一带的夜来祭资讯交流馆

高知夜来祭使用的彩色鸣子
我们对高知的夜来祭很感兴趣,于是走进资讯交流馆。 这次穿上表演服装拍了照,也动手制作了一对鸣子,摇起来会发出清脆的声音。
馆内介绍祭典的历史与舞蹈文化,即使没有赶上夏季活动,也能先认识它的特色。 现在鸣子制作有独立费用与预约安排,想参加的话,先查当日名额和所需时间。
[高知夜来祭资讯交流馆www.honke-yosakoi.jp](https://www.honke-yosakoi.jp/)
## 龙马诞生地纪念馆

纪念馆内的坂本龙马像

纪念馆的木质庭院与人物像
接着,我们去龙马诞生地纪念馆。 这里通过资料、影像和模型,介绍坂本龙马成长的街区与时代背景。 当天志愿讲解员的说明很详细,也让我们多了解了幕末高知的生活。
纪念馆位于上町地区,从市中心过去需要再安排一段交通。 若对这段历史特别感兴趣,可以在这里多留时间,减少后面的赶路。
## 美术馆与回到城西馆的一晚

高知县立美术馆入口与拍摄时的展览告示
这次市内参观的最后一站,是高知县立美术馆。 馆内的收藏与当期展览各有重点,准备入馆时可以先看正在举办的主题,再决定停留多久。
[高知县立美术馆:展览与开放日moak.jp](https://moak.jp/)

这次入住的城西馆
傍晚,我们回到城西馆。 一天走下来,晚餐与浴场便成了很实际的期待。 住宿方案、浴场开放和交通接驳各有安排,可以在预订时一并确认。
[城西馆官方网站www.jyoseikan.co.jp](https://www.jyoseikan.co.jp/)
## 路面电车怎样搭配
- 🚋 高知站出发:查询经播磨桥换乘、前往高知城前的路线。
- 🚶 城市中心:高知城、市场与带屋町可按体力搭配步行。
- 🚋 上町及美术馆方向:另查电车路线,给换乘和从车站到入口的步行留下余量。
一日券的适用区域与普通票价可能调整,先对照当天实际路线选择即可。 如果还想去五台山或桂滨,可以另外留一天,利用适合郊外景点的交通慢慢走。
### 从函馆到青森大间的渡轮之旅
URL: https://www.sakashita.page/hakodate-to-oma-ferry/
Last updated: 2026-10-23T21:09:59.000Z
结束函馆的旅行后,我和朋友选择乘船前往青森县大间町。 这段航程横渡北海道与本州之间的津轻海峡,大约需要90分钟。 对我们来说,它不只是换一个目的地,也是看海上日落、认识同行乘客的一段旅途。
## 十一月的下午,从函馆离港

津轻海峡航线的港口候船建筑
那天晴朗,我们期待着海上的日落,也期待到了大间以后吃一顿金枪鱼料理。 出发前已经在网上订好大函丸的船票,下午到函馆航站楼办理手续,再按现场引导排队登船。 当时乘坐的是16时出发的班次,船离港后,岸边的建筑渐渐退到身后。
这里记录的是当年的十一月行程。 现在安排同一段旅行时,班次、票价、舱等和提前办理手续的要求,应从[津轻海峡渡轮官网](https://www.tsugarukaikyo.co.jp/)确认。 步行乘客与携车乘客的流程有所不同,订票时也要选对人数、车辆及车长等项目。

停靠在码头的津轻海峡渡轮
大函丸的白色船身上有海豚图案。 船公司公布的[船舶资料](https://www.tsugarukaikyo.co.jp/guide/ferry/daikanmaru/)列有客舱与船内设施,选择舱等前可以先看看座位和休息空间的样子。 车辆甲板、客舱和露天区域是不同的空间,上船后先看示意图,找到自己的位置,也记住往返甲板的路线。
## 窗边的一场闲聊

渡轮上靠窗的公共休息区
找到座位以后,我去自动贩卖机买饮料。 回去时路过公共休息区,看见金枪鱼形状的桌子与一排朝海的窗户,便走到窗边。 一位穿和服的女士也过来坐下,我们点头微笑,我用日语向她问好。
船稳稳地航行,窗外有飞过的海鸥,夕阳把海面照得发亮。 我忍不住拿出手机拍照,她见我兴奋,便和我聊了起来。 她说自己住在札幌,这次要回大间老家。
我注意到她腰带上的小配饰,夸了一句“かわいい”。 她把配饰取下来给我看,还教我用当地的说法“めんこい”来表达可爱。 话题又转到温泉,她笑着说,身边不少人会在车里备好泡汤用品,想去时就能出发。 这是她讲给我的日常印象,也让我对接下来要去的地方多了一点亲近感。 临别前,她还推荐了一家大间的餐馆。

夜色中的大函丸与港口码头
太阳慢慢落下,窗外暗了,十一月的寒意也变得明显。 我回到客舱座位,盖上备用的小毯子,和朋友小声聊着天,分享刚刚拍到的照片。 不久以后,船抵达大间。 从登船时明亮的下午,到下船时的夜色,海峡两岸的移动有了很具体的时间感。
## 到大间吃一顿晚饭

大间食事处馬い家的夜间店面
下船后,我们去了女士推荐的餐馆“馬い家”。 店内朴实,带着居酒屋的气氛。 我们问起推荐菜,店家介绍了当天的海鲜,于是点了两份刺身套餐,又加了毛豆和啤酒。

在大间品尝的金枪鱼刺身套餐
刺身切得厚实,配上热饭,是抵达后的第一顿满足晚餐。 饭后还有焦糖布丁,柔软的口感给这一天收了尾。 记得当时人均花费约3000日元,这是那次用餐的金额,之后的菜单与价格不能直接照搬。
[大间町观光协会的店铺页](https://oma-wide.net/menu/oshokuzidokoroumaiya/)列有餐馆的名称、位置与营业信息。 港口、餐馆和住宿地点不一定在同一片区域,订好船票以后,最好把下船后的交通与晚餐也接起来安排。 晚班船抵达时天色可能已暗,若还要转乘或取车,行李和时间都要一起考虑。
这次让我记住的,除了海峡上的夕阳,还有那场很自然的闲聊。 一个小配饰、一句地方说法和一家餐馆的推荐,把原本只是交通的一段路,变成了旅行本身。
### 秋田田泽湖之旅
URL: https://www.sakashita.page/lake-tazawa/
Last updated: 2026-10-23T21:00:00.000Z
田泽湖位于秋田县仙北市,水深约423.4米,是日本最深的湖泊。 吸引人的不只有一个深度数字,还有湖面、山林与岸边地形在不同光线下形成的颜色。 安排旅行时,先选几处湖岸停留点,再决定是否加上游船、骑行或温泉,会比把周边所有活动都排进去更舒服。
## 田泽湖站,不在湖边
乘铁路到田泽湖站以后,还需要巴士、出租车或自驾进入湖畔。 白滨一带、御座石神社与辰子像分布在不同位置,不能把“到了田泽湖”理解成它们都在步行几分钟的范围内。
如果依赖巴士,应先看回程与环湖方向。 想在某一站下车停留,也要确认下一班什么时候来,不要只按汽车绕湖的时间估算公共交通行程。
## 辰子像:湖西岸的传说
辰子像位于湖西岸的潟尻一带,并非北岸。 雕像由舟越保武制作,1968年揭幕,表现的是地方传说中的辰子。 她为求青春与美貌而化为龙神的故事,有不同讲述版本,应作为民间传说来读,不是湖泊形成的科学解释。
[仙北市的雕像介绍](https://www.city.semboku.akita.jp/sightseeing/spot/04%5Ftatsukozou.html)提供位置与相关历史。 看完雕像,也可以转身看看岸边与对岸山势,不必把这一站缩成一张人物合影。
## 游船:另一种看湖的距离
游船从白滨一带出发,经潟尻等位置周游。 航线、停靠与运营季节有自己的安排,不是全年随到随开的水上交通。 如果准备在途中上下船,应把同一条航线的停靠与后续班次一起确认。
[运营方的游船页面](https://tazawako-resthouse.jp/yuransen-2)列有当季运行信息。 风浪与天气也可能影响航行,行程里最好保留不搭船时仍能散步的选择。
## 步行、骑行与水上活动怎么选
想看一小段湖岸,步行就足够。 想环湖骑行,则要另外考虑距离、坡度、道路交通与还车时间,不能把湖岸道路一概当成与机动车完全分离的自行车道。
水上活动应使用明确开放的场地与有相应安排的服务。 湖水很深,眼前清澈也不代表任意湖岸都适合下水。 只想静静看湖的人,选择一处开放岸边坐一会儿,也能有完整的体验。
## 周边温泉与住宿,留给另一段时间
田泽湖周边与更远的玉川温泉等地区,交通和营业季节各有条件。 去温泉不应只因为地图上看起来“在附近”,还要把实际道路与往返时间算清。 泡汤是旅行安排,不需要附上未经核实的治疗或改善健康承诺。
半天可以专注湖畔,一整天再按交通加入其他地点。 湖面会随光线改变,留一点空白时间,往往比再多赶一个景点更容易记住田泽湖。
### GitHub ActionsのSecret管理から解放されよう
URL: https://www.sakashita.page/doppler-secrets-management-github-actions/
Last updated: 2026-10-20T22:59:59.000Z
GitHub ActionsのSecret管理が面倒だなあと思ったことはないだろうか。
RepositoryごとにSecretを登録して、Environmentごとに設定して。そのあとに、APIキーを更新したらGitHub側も忘れずに更新して……。
大した手間ではない作業なのだが、Repositoryや抱えているプロジェクトが増えてくると、ちりつもでだんだん面倒になってくる。
特に複数のサービスで同じSecretを使っていると、変更するたびにあちこち更新する必要がある。
そんなあなたに、**Doppler**というサービスを紹介したい。
[Doppler | Secrets management for humans and AI agentsDoppler's secrets management platform helps teams secure, sync, and automate secrets across cloud and on-prem environments with security and developer-friendly workflows.Secrets management for humans and AI agents](https://www.doppler.com)
## Doppler is 何。
一言でいうと、DevOpsにおけるSecret管理をいい感じに自動化してくれるサービス。
APIキー、アクセストークン、データベースの接続情報といったアプリケーションの動作に必要な機密情報から、署名や証明書などSecretと名のつくものならなんでも一元管理できる。
例えば、今までのこんな感じのワークフローをだいぶイケイケにできる。
1. 1PasswordからAPIキーをコピーする
2. GitHubのRepositoryの設定を開く
3. Actions Secretsに登録する
4. 別のリポジトリにも同じSecretを登録する
5. APIキーを更新したら、登録した場所を思い出して全部更新する
当たり前すぎて特に何も感じていなかったが、いや、面倒すぎる。
しかも、更新を忘れたらCI/CDがコケるし、署名回りなんかはコケたらだいぶめんどい。
Dopplerを使えば、こうしたSecretの更新作業周りを楽にできる。
Doppler上でSecretを管理し、GitHub Actionsなどの外部サービスに自動で同期できるからだ。
つまり、**Secretを更新する場所をDopplerに集約できる**。
集約することがこのサービスの核心であり、それだけと言えばまあそれだけなのだが、これがまあ、非常に便利。
## GitHub Actionsとの連携が便利
公式のGitHub Integrationを利用すると、Dopplerに登録したSecretをGitHub ActionsのSecretsに自動同期してくれる。
[Github Actions | Doppler DocsBring new features and workflows for managing repository secrets with our GitHub integration. This guide includes step-by-step instructions, including prerequisites and authorizations.Doppler](https://docs.doppler.com/docs/github-actions)
Dopplerでプロジェクトを作成し、GitHub Integrationから連携するリポジトリとConfigを指定するだけなので、設定もらくちん。
一度連携してしまえば、あとはDoppler側でSecretを追加・更新・削除すると、GitHub側にも変更が反映される。
つまり、毎回GitHubのSettingsを開いてSecretをポチポチ登録する必要がなくなる。
素晴らしい。
ちなみに、GitHub Actionsだけでなく、CodespacesやDependabotなどへの同期にも対応している。
GitHubで開発をしている多くの開発者にとっては使いやすいはずだ。
[GitHub | Doppler DocsConnect Doppler with GitHub to access your secrets in Actions or CodespacesDoppler](https://docs.doppler.com/docs/github)
### 環境ごとにSecretを分けられる
Dopplerならではの概念だが、人によっては便利だと思うのが、環境ごとのSecret管理だ。
例えば、同じアプリケーションでも、Development、Staging、Productionでは異なるAPIキーや接続情報を使うことが多いだろう。
これをGitHub Actionsだけで管理しようとすると、Environment Secretsを設定したり、命名規則を工夫したりする必要がある。
Dopplerでは、プロジェクトの中に環境ごとのConfigを用意して、それぞれのSecretを管理できる。
例えば、こんな感じ。
```
my-awesome-app
├── dev
│ ├── DATABASE_URL
│ └── API_KEY
├── stg
│ ├── DATABASE_URL
│ └── API_KEY
└── prd
├── DATABASE_URL
└── API_KEY
```
環境ごとに異なる値を持たせつつ、一つのサービスから管理できるというわけ。
GitHub ActionsのEnvironment Secretsとの連携にも対応しているので、Enviromentを分けて運用しているプロジェクトでも使いやすい。
こういう地味な管理作業を減らしてくれるのはありがたい。
### Secretの管理元を統一できる
もう一つ重要なのが、Secretの管理元を明確にできること。
例えば、同じAPIキーがGitHub Actions、デプロイ先のサービス、ローカル開発環境などに散らばっていると、どれが最新なのか分からなくなることがある。
DopplerをSecretの管理元として統一すれば、少なくとも「どこで更新するべきか」は明確になる。
ちなみに、DopplerのGitHub Integrationは基本的にDopplerからGitHubへの一方向同期なので、何れにしてもDopplerへ集約する方が運用として楽になる。
GitHub側でSecretを変更しても、その値がDopplerに反映されるわけではない。
既存のGitHub Secretsも、連携するだけで自動的にDopplerへ取り込まれるわけではないので注意。
**Dopplerを導入したら、以降のSecret更新はDoppler側で行う**、という運用にするのがよさそう。
[Github Actions | Doppler DocsBring new features and workflows for managing repository secrets with our GitHub integration. This guide includes step-by-step instructions, including prerequisites and authorizations.Doppler](https://docs.doppler.com/docs/github-actions?utm%5Fsource=chatgpt.com)
## Infisicalという選択肢もある
ちなみに、競合サービスとしてはInfisicalなどがある。
[Infisical | The modern security platform for developers and agentsInfisical is the all-in-one identity security platform for developers, machines, and AI agents. Manage identities, secrets, certs, and access.Infisical](https://infisical.com/)
競合サービス|Infisical
InfisicalもSecretの一元管理や外部サービスとの連携に対応している。
どちらもSecret管理を効率化するという基本的な目的は同じ。
ただし、提供されている機能やプラン、セルフホストへの対応方針などには違いがあるので、用途に応じて選ぶのがよいと思うが、後述するが学生ならDopplerがおすすめだ。
## 学生なら無料だよ
さて、ここからが本題と言っても過言ではない。
Dopplerはなんと、**学生向けにTeamプランを無料で提供している**。
GitHub Educationと提携しており、GitHub Student Developer Packの対象者であれば、通常は月額21ドル/ユーザーのTeamプランを無料で利用できる。
[SecretsOps for students: Master secure DevOps with Doppler's student planKickstart your DevOps journey with Doppler's SecretsOps plan for students. Gain hands-on experience managing secrets and automating security.](https://www.doppler.com/secretsops-for-students)
GitHub Student Developer Packといえば、学生向けにさまざまな開発ツールの特典が提供されている、あれである。
Dopplerもその一つというわけだ。
学生開発者としては非常にありがたい。
GitHub Actionsを使っている学生なら、試してみてはいかがだろうか。
[SecretsOps for students: Master secure DevOps with Doppler's student planKickstart your DevOps journey with Doppler's SecretsOps plan for students. Gain hands-on experience managing secrets and automating security.](https://www.doppler.com/secretsops-for-students)
### 学生割引はアカウント登録時に要注意
学生向けの割引を受けるには、GitHub Student Developer Packの認証が有効なGitHubアカウントを使う必要がある。
GitHub Student Developer Packの認証が有効なGitHubアカウントを使ってGitHub SSOでログインし、新しいWorkplaceを作成することで割引が適用される仕組みになっている。
既存のWorkplaceに自動で割引が適用されるとは限らないので、そこは注意。
もし割引が反映されない場合は、一度ログアウトしてGitHub SSOでログインし直し、新しいWorkplaceを作成する方法が公式に案内されている。
[SecretOps for Students Program not applying discountOur SecretOps for Students Program is available for students who have signed up for GitHub's Student Developer Pack. If you've signed up for that pack and are trying to access our SecretOps for Stu…DopplerJoel Watson](https://support.doppler.com/hc/en-us/articles/12778257327387-SecretOps-for-Students-Program-not-applying-discount)
ミスっても、初期はTrialで始まるはずなので、プランと請求情報を確認しておこう。
[SecretOps for Students Program not applying discountOur SecretOps for Students Program is available for students who have signed up for GitHub's Student Developer Pack. If you've signed up for that pack and are trying to access our SecretOps for Stu…DopplerJoel Watson](https://support.doppler.com/hc/en-us/articles/12778257327387-SecretOps-for-Students-Program-not-applying-discount)
## Secret管理はまだ残るが。
便利なDopplerだが、当然ながら導入すればSecret管理について何も考えなくてよくなる、というわけではない。
例えば、どのリポジトリにどのSecretを同期するのかは、適切に設定する必要がある。
Production用のAPIキーをDevelopment環境に渡してしまったら本末転倒だ。
また、Secretを複数のサービスに同期する以上、同期先のアクセス権限やセキュリティについても考える必要がある。
GitHub Actionsとの連携では、同期したSecretは引き続きGitHub側にも保存される。
Dopplerに集約したからといって、同期先のSecretがなくなるわけではない。
さらに、Secretの変更が自動で同期されるのは便利だが、変更によってアプリケーションが動かなくなる可能性は当然ある。
そのため、Production環境のSecretを更新するときは、これまでどおり慎重に運用する必要がある。
とはいえ、手作業での転記や更新漏れを減らせるメリットは大きい。
**Secretの安全性を考えながら、面倒な管理作業を減らせる**のがDopplerのよいところだろう。
## 終わりに
GitHub ActionsのSecret管理は、プロジェクトが小さいうちはそれほど気にならないし、おそらく皆そう思っているだろう。
しかし、リポジトリや環境が増えてくると、だんだん面倒になってくる。
そうした管理作業を自動化できるDopplerは、なかなか便利なサービスだ。
特に学生ならTeamプランを無料で利用できるので、試してみるハードルも低い。
Secret管理に時間を使うより、もっとProductiveなことをしよう。
皆様に、快適なDevOps生活があらんことを。
### ChatGPTをPro 500で使う
URL: https://www.sakashita.page/openai-pro-500-usage-limit-bug-compensation/
Last updated: 2026-10-20T16:59:59.000Z
ChatGPTのプランが刷新され、ヘビーユーザー向けのプランが3 Tierに分割された。
Pro 100/200/500で分かれたのだが、エンジニアとしてAIを使いまくる身としてはPro 500一択である。
ということで、お布施で配られたCreditを使い切ったタイミングでPro 500プランに変更。
だが、あれ?思ったほど使える感じがしない...?
まあ気にせず使っていたのだが、OpenAIからメールが。
> Hello,
>
> We’re contacting you because an error prevented your weekly Codex and Work usage allowance from increasing when you upgraded from Pro 200 to Pro 500\. Your account kept the allowance from your previous plan, so you received less usage than your new subscription included.
>
> We corrected the error on October 16, and your weekly allowance now matches your Pro 500 plan. We’re sorry for the error. We’ve added $1,000 worth of credits (25,000 credits) to your account to help make up for it. These credits are separate from your regular weekly allowance, which will continue to match your current plan. These credits expire on January 20, 2027.
とのこと。
どうやらプラン変更でUsageがリセットはされたものの、Allowance自体はPro 200のままだった模様。
ということで、Creditのお布施をしたよ、ごめんね、ということかな。

確かに増えてる
こんなこともあるんだ。
Creditも有効期限があるので、使い切らねば。
### Dependabotの対応が面倒なのでRenovateを導入する
URL: https://www.sakashita.page/switching-from-dependabot-to-renovate/
Last updated: 2026-09-25T13:00:24.000Z
Dependabotくんはある程度の依存関係の更新を通知してくれるが、山のようにprojectを抱えているとなんとも痒いところに手が届かない。
ということで、Renovateを使ってみる。
[Build software better, togetherGitHub is where people build software. More than 150 million people use GitHub to discover, fork, and contribute to over 420 million projects.GitHub](https://github.com/apps/renovate)
ここからGitHub Appを入れる。
あとは、指示に従ってポチポチするだけで、超かんたんだった。
## 感想
自動化最高。
### nodejsのバグを見つけたのでPRを投げてみた
URL: https://www.sakashita.page/nodejs-v22-test-runner-backport/
Last updated: 2026-09-17T06:02:15.000Z
## 概要
要するに、node --testで特定のバイナリを標準出力に書くと、テストランナーが固まったり、異常終了したりするバグです。
バグ自体は既知のものだったようで、修正がされていました。
[test\_runner: infinite loop in FileTest#drainRawBuffer when child stdout contains FF 0F followed by large size bytes · Issue #62693 · nodejs/nodeVersion v25.9.0 (also reproduced on v24.14.1 LTS, v22.21.0 LTS, v23.4.0) Platform Darwin 25.3.0 Darwin Kernel Version 25.3.0: Wed Jan 28 20:53:05 PST 2026; root:xnu-12377.81.4\~5/RELEASE\_ARM64\_T6020…GitHublslv1243](https://github.com/nodejs/node/issues/62693)
ただ、手元で使っていたv22では修正が配布されていなかったので、PRを投げたという形です。
[\[v22.x backport\] test\_runner: avoid hanging on incomplete v8 frames by P4suta · Pull Request #65807 · nodejs/nodeBackport #62704 and #64706 together to v22.x-staging to fix test-runner hangs and deserialization errors triggered by binary child stdout. This follows up on #64706 (comment). Why backport to Maint…GitHubnodejs](https://github.com/nodejs/node/pull/65807)
今回取り込んだのは、無限ループを防ぐ修正と、データの長さを符号なし整数として扱う修正の2つです。
## 感想
V8のビルド時間が長かった。作業時間的には3時間ぐらい。
## 続編
PRを投げてしばらく待っていたら、続々とApproveがされていった。たまに裏の自動化システムがCIを走らせてくれたのだが、ことごとくfail。
何かまずいことでもしたのかなとJenkinsの方を見てみてもPermissionがなく何も見れないので、静観を決め込む。

Jenkinsのログは見れず....
それとは別に頻繁に出されるCIのリクエスト、どれも撃沈。

何が起きているんだろうなあと思いつつも、よくわからないので静観していたわけだが、リリース担当者からコメントが投稿される。
> Sadly this is not gonna make it to the v22.23.3 release due to CI and timing. I've pospone this release too much now.
[\[v22.x backport\] test\_runner: avoid hanging on incomplete v8 frames by P4suta · Pull Request #65807 · nodejs/nodeBackport #62704 and #64706 together to v22.x-staging to fix test-runner hangs and deserialization errors triggered by binary child stdout. This follows up on #64706 (comment). Why backport to Maint…GitHubnodejs](https://github.com/nodejs/node/pull/65807#issuecomment-5684303117)
ということで、次回リリースに間に合わないんだと。原因はCIがコケてること。
ほうほう、まあ仕方ないなあと思いつつ、どうやらCI起因で相手も大変そうなので私の方でできるかわからないが調査してみることに。
完全なログではないが、ざっくり何が起こっているかはnodejs/reliability/reportsにまとまっていることが分かったので軽く眺めてみることに。
[reliability/reports/2026-09-11.md at main · nodejs/reliabilityMake the CI green again. Contribute to nodejs/reliability development by creating an account on GitHub.GitHubnodejs](https://github.com/nodejs/reliability/blob/main/reports/2026-09-11.md)
9/11から9/16までのログを眺めてみると以下のエラーが出ているとわかる。
```cmd
Error: [WinError 5] Access is denied: '...\clcache\stats.txt.new' -> '...\clcache\stats.txt'
```
なるほど、どうやらビルドでclcacheとやらが上手くいっていないようだな、と。
[build/jenkins/scripts/windows/compile.cmd at 34fc8413cf179a8f08a9653eef3763da6c7fefef · nodejs/buildBetter build and test infra for Node. Contribute to nodejs/build development by creating an account on GitHub.GitHubnodejs](https://github.com/nodejs/build/blob/34fc8413cf179a8f08a9653eef3763da6c7fefef/jenkins/scripts/windows/compile.cmd)
件のファイルはこれ。
clcacheというのはfrerich/clcacheというプロジェクトのもので、いってしまえばnodejsに関係なく上流の問題というわけ。
[GitHub - frerich/clcache: A compiler cache for MSVC, much like ccache for gccA compiler cache for MSVC, much like ccache for gcc - frerich/clcacheGitHubfrerich](https://github.com/frerich/clcache)
さて、じゃあfrerich/clcacheにPR出せばいいかと思いきやそうともいかない。
というのも、すでにこのプロジェクトはarchiveされているのである。readonlyなので新規にどうもこうもできない。
いやはやここで手詰まりか、いやそんなことはないのだ。
調べてみれば、全く同じ問題にぶち当たっている人がいるではないか!
[PermissionError: \[WinError 5\] Access is denied if using build multithreads > 3 · Issue #342 · frerich/clcacheHello, I’m enabling the clcache in the Uefi firmware MSVC build toolchain. I’ve enable thed ccache in our GCC/CLang toolchains and I hope the clcache can work in our MSVC toolchain as well. I built…GitHubshijunjing](https://github.com/frerich/clcache/issues/342)
ここの書き込みによれば、次の通りである。
> you should move clcache dir, not clcache binary. It can be configured through CLCACHE\_DIR environment
> It works with threads > 4 after I set `CLCACHE_DIR=C:\clcache_dir`
ということなので、nodejsでも同じ対応をしようじゃないか。
[Windows CI: clcache fails with ”\[WinError 5\] Access is denied” on stats.txt · Issue #4471 · nodejs/buildnode-compile-windows has been failing since 2026-09-11 with: Error: \[WinError 5\] Access is denied: ‘C:\\Users\\Administrator\\clcache\\stats.txt.new’ -> ‘C:\\Users\\Administrator\\clcache\\stats.txt’ Scope…GitHubP4suta](https://github.com/nodejs/build/issues/4471)
調査結果と修正をIssueで起票してみた
ということで、作ってみたPRがこちら。
[jenkins,win: move clcache cache out of user profile by P4suta · Pull Request #4472 · nodejs/buildFixes #4471 compile.cmd sets CLCACHE\_BASEDIR but not CLCACHE\_DIR, so clcache stores its cache in %USERPROFILE%\\clcache. That is the configuration reported in frerich/clcache#342 to fail with: Error…GitHubnodejs](https://github.com/nodejs/build/pull/4472)
変更内容は `jenkins/scripts/windows/compile.cmd` に `set CLCACHE_DIR=C:\clcache_dir` を追記するだけ。
一旦これで直るはず。即効性が欲しい時は `DISABLE_CLCACHE=1` でイケるはずと、Issueにも書いておいた。
上手くいくといいな。
### libarchive_oxideはじめました
URL: https://www.sakashita.page/libarchive_oxide/
Last updated: 2026-07-22T23:00:11.000Z
[libarchive](https://www.libarchive.org/)をRustで作り直しています。
[GitHub - P4suta/libarchive\_oxide: Unified safe-Rust archive library with a no\_std coreUnified safe-Rust archive library with a no\_std core - P4suta/libarchive\_oxideGitHubP4suta](https://github.com/P4suta/libarchive%5Foxide)
> `libarchive_oxide` aims to become a safe artifact engine for OCI layers, packages, and mainstream archive CLI workflows. It is not a promise to clone every historical libarchive format or symbol.
>
> The preferred product surface is a high-level Rust engine with `inspect -> plan -> apply/create`, finite resource limits, extraction policy, capability-based filesystems, bounded streaming events, and range sources. Low-level readers, writers, and sans-I/O state machines remain available for specialized integrations.
ということで、要するにめちゃくちゃモダンでイケイケなlibarchiveを作ろうぜ!というパッションです。
一方で、OS依存部分や実際にクラウドで採用されるためのSDK部分などについてはプロジェクトの負担が大きすぎるので夢見てはいますが、実質的にはベンダーとの協力を仰がなければならない部分であり、私としましては積極的な協力が行えるような下地の準備に全力を注ぎたいと思います。
とはいえ、まずはむちゃくちゃ使いやすいモダンでイケイケなlibarchiveを作りこむという意味で、coreに集中したいと思います。
> Registry clients, cloud SDK implementations, GUIs, dynamic plugin ABIs, complete libarchive source compatibility, and silent OS-metadata loss are not part of the engine. Historical long-tail formats can graduate as external providers when they have real usage data, ownership, and conformance corpora.
### 青空文庫のParser
URL: https://www.sakashita.page/aozora-pure-rust-parser/
Last updated: 2026-07-22T20:24:22.000Z
青空文庫のParserを作ってます。
[GitHub - P4suta/aozora: Pure-functional Rust parser for 青空文庫記法 (Aozora Bunko notation): ルビ, 傍点, 縦中横, 外字, 返り点, indent containers, page breaks.Pure-functional Rust parser for 青空文庫記法 (Aozora Bunko notation): ルビ, 傍点, 縦中横, 外字, 返り点, indent containers, page breaks. - P4suta/aozoraGitHubP4suta](https://github.com/P4suta/aozora)
Rustで作ってますが、配布物はcargoのほかにnpm/pypiでも入手できます
## Documentation
- [Playground](https://p4suta.github.io/aozora/playground/) — try it in the browser
- [API reference](https://docs.rs/aozora)
- [Notation specification](https://p4suta.github.io/aozora-notation-spec/) — the normative spec this parser is tested against
## 雑記
epubへの変換や既存のテクストを解釈するだけのシステムなら既に様々存在しています。
本プロジェクトでは、仕様の再策定から行い、今後の青空文庫コーパスの作成に役立てられるような基盤を提供したいと考えています。
LSP/Linter/Formatterなども提供しています。
今後の青空文庫の発展を願って。
### イケイケなWinスクショアプリを作りました
URL: https://www.sakashita.page/snaply-released/
Last updated: 2026-07-20T23:00:29.000Z
パソコンのスクショってなんというか、地味というか、撮ってそのままだと何とものっぺりしていますよね。
特にWindowsだとMacっぽい丸っこい感じとかもなく、無骨だなあと。
ということで、それっぽくおしゃれな背景や角丸を付けておしゃれに撮影をしてくれるスクショアプリを作りました。
[GitHubで公開しています。](https://github.com/P4suta/Snaply)
[GitHub - P4suta/Snaply: Snaply — a modern, clean-architecture Windows screenshot tool (WinUI 3 / .NET 10)Snaply — a modern, clean-architecture Windows screenshot tool (WinUI 3 / .NET 10) - P4suta/SnaplyGitHubP4suta](https://github.com/P4suta/Snaply)
ウィンドウ・領域選択・スクリーンのキャプチャができます。
タッチスクリーンなどにも対応しているはずです。
撮影をすると、クリップボードにコピーされ、フォルダーへの保存もされます。
### Find My Files
URL: https://www.sakashita.page/find-my-files/
Last updated: 2026-07-19T23:00:01.000Z
Windowsにおける爆速ファイル検索アプリの定番といえば、[Everything](https://www.voidtools.com/)です。
[voidtoolswebhost2](https://www.voidtools.com/)
その高速さの理由は、NTFSの仕組みをうまく活用していることにあります。Windowsで一般的に使われているNTFSには、ファイル情報を管理するマスターファイルテーブル(MFT)と、ファイルシステムの変更履歴を記録するUSN Journalがあります。Everythingはこれらを利用することで、初回に高速にファイル一覧を構築し、その後はUSN Journalを監視してリアルタイムにインデックスを更新しています。そのため、数百万ファイル規模の環境でも非常に高速な検索を実現しています。
もちろん、単純にファイル名やパスを保持するだけでは、大量のメモリを消費してしまいます。また、数百万件ものファイルから一瞬で目的のものを見つけるためには、メモリ効率の高いデータ構造や高速な検索アルゴリズムなど、さまざまな工夫が必要になります。
このように、Everythingは見た目こそシンプルですが、中身はかなり高度なことをやっているソフトウェアです。
なお、EverythingはMITライセンスを採用していますが、公開されているのはSDKなど一部のコンポーネントのみで、アプリケーション本体のソースコードは公開されていません。
一方で、UIはどちらかというと昔ながらのWindowsアプリという印象で、機能性を重視したデザインになっています。
そこで、「Windows 11らしいFluent DesignのUIで、Everythingのような体験を実現したら面白いのでは」と思い、[**find my files**](https://github.com/P4suta/find-my-files)というプロジェクトを作りました。
コア部分はRustで実装しています。
ぜひ使ってみてください!
[GitHub - P4suta/find-my-files: FOSS Everything-style instant file-name search for Windows. Rust engine + native WinUI 3.FOSS Everything-style instant file-name search for Windows. Rust engine + native WinUI 3\. - P4suta/find-my-filesGitHubP4suta](https://github.com/P4suta/find-my-files)
### Rust libfprint?
URL: https://www.sakashita.page/rust-libfprint/
Last updated: 2026-07-18T22:59:59.000Z
指紋認証は便利で快適です。
iPhoneはFaceIDを使うので別次元の快適さですが、PCでは指紋認証が現役です。
Windows/Macはいざ知らずいざ知らず、Linuxで指紋認証デバイスを使おうと思ってもPlug-and-Playというわけにはいきません。
それどころか、Linuxでの指紋認証サポートは限定的です。
Linuxでは[fprint project](https://fprint.freedesktop.org/)が主体となって(事実上のデファクトとして)指紋認証デバイスのサポートを行っていますが、指紋認証デバイスの仕様をメーカーが公開していない以上、指紋認証デバイスAを持つユーザーが自分でドライバAを書くほかありません。
メンテナーによる骨の折れる作業と血のにじむ努力によって多数のデバイスがLinuxでサクッと動くようになり、Linux Mintなどの一部ディストロではGUIから気楽に使えるようになったので、対応デバイスをお持ちの方にとってはとてもいい時代になりました。
しかし、まあ、内部実装が歴史的につらい事情などなど、現代的に作り直すことで歴史から解き放ちつつも、共存の道を模索するプロジェクトとして[fprintd](https://github.com/P4suta/fprintd)というプロジェクトを私が立ち上げました。
[GitHub - P4suta/fprintd: A GObject-free, pure-Rust fingerprint stack that speaks fprintd’s D-Bus contract (net.reactivated.Fprint): daemon, NBIS MINDTCT/BOZORTH3 ports, and an embeddable core.A GObject-free, pure-Rust fingerprint stack that speaks fprintd’s D-Bus contract (net.reactivated.Fprint): daemon, NBIS MINDTCT/BOZORTH3 ports, and an embeddable core. - P4suta/fprintdGitHubP4suta](https://github.com/P4suta/fprintd)
小難しくいっていますが、要するにRustで実装したよ、ということです。
## MINDTCT
指紋画像から特徴量を抽出するのに用いられるアルゴリズムの一つにNISTが開発したMINDTCTが用いられます。
本プロジェクトではMINDTCTを[Rustで実装し、公開しています](https://github.com/P4suta/fprintd/tree/main/crates/fprint-mindtct)。
## Bozorth3
指紋照合に広く用いられるアルゴリズムのひとつにNISTが開発したBozorth3が用いられます。
本プロジェクトではBozorth3を[Rustで実装し、公開しています](https://github.com/P4suta/fprintd/tree/main/crates/fprint-bozorth3)。
## プロジェクトの今後
結局のところ、メーカーが仕様を公開しない限り、我々はいつまでもリバースエンジニアリングの沼のままです。
私自身このプロジェクトのためにこの世のすべての指紋認証デバイスを入手し、対応するドライバを書き上げ……といったことはできませんが、逆に皆さんが「私の指紋認証デバイスが動いたらうれしいな。」ということでしたら、各種準備は整えておりますのでお手元のAI Agentを使って私のプロジェクトの中にドライバを書けばいいかと思います。もし、ほかに世界の誰か同じデバイスを使っている人のためになるかも、と思ってくださる方はPull Requestなどで協力していただけると幸いです。
### Claude Code: API Error: Unable to connect to API (ConnectionRefused)
URL: https://www.sakashita.page/claude-code-api-error-unable-to-connect-to-api-connectionrefused/
Last updated: 2026-07-22T20:09:48.000Z
先日Claude Codeをイジイジしていたら、前触れもなく`API Error: Unable to connect to API (ConnectionRefused)`とかいう謎のエラーをはいてうんともすんとも言わなくなった。
イヤー困った。
> [Unable to connect to API (ConnectionRefused)](https://www.reddit.com/r/ClaudeCode/comments/1r9d6oe/unable%5Fto%5Fconnect%5Fto%5Fapi%5Fconnectionrefused/)
> by [u/Pristine\_Ad9316](https://www.reddit.com/user/Pristine%5FAd9316/) in [ClaudeCode](https://www.reddit.com/r/ClaudeCode/)
Redditを見るとDNSを見ろとのこと。
curlでAnthropicのAPIエンドポイントをたたくと確かにDNS周りでなんだか不穏な様子。
`/etc/resolv.conf`を書き換えろと言われるが、なんとも強引すぎやしないかと思い、そっ閉じ。
ダメもとで`sudo shutdown -h now`でお茶を濁したところなんと解決しましたとさ。
### Pure Rust Webp Implementation
URL: https://www.sakashita.page/pure-rust-webp-implementation/
Last updated: 2026-07-18T00:30:08.000Z
webpをPure Rustで実装しました。
[GitHub - P4suta/webpkit: Pure-Rust WebP codec (VP8L lossless + VP8, encoder & decoder, no\_std, forbid-unsafe)Pure-Rust WebP codec (VP8L lossless + VP8, encoder & decoder, no\_std, forbid-unsafe) - P4suta/webpkitGitHubP4suta](https://github.com/P4suta/webpkit)
libwebpよりも使いやすく、モダンな作りになっています。
`no_std`など、汎用的に使えると思います。
Rustで実装をする以上、unsafeがあると意味が薄れてしまいますので、本実装ではunsafeはありません。
また、ちょっとしたことですし、webpに限ったことじゃないのですが、webpにおけるデファクトであるlibwebpではメタデータがデフォで捨てられます。画像ライブラリにとっての至上命題は画像ファイルを小さくすることであり、メタデータだってデータなわけで捨てたほうが当然ファイル容量は削減できるので当然といえば当然であり、ことwebpなどのモダンな画像コーデックとしてはプライバシーという名目で捨てたほうがむしろ合理的ともいえます。一方で、メタデータの中にはexifだけでなくカラープロファイルなどのデータなどもあり利用者としては勝手に捨てられると困るデータもあるわけで、やや標準から外れますが、本実装ではメタデータは標準で捨てない設計としております。
### 1Passwordを閉じるボタンが……ねえ!
URL: https://www.sakashita.page/1password-window-buttons-missing-fullscreen/
Last updated: 2026-05-15T21:00:17.000Z
1Passwordを使っていたら、いつの間にかウィンドウの **閉じる/最小化/最大化ボタンが消えていた**。Ctrl+Wでウィンドウ自体は閉じられるので長らく放置していたけれど、調べてみたら原因がしょうもなかったので共有しておく。
💡
結論
`F11`を押してみよう
## 症状
- ウィンドウ右上の最小化・最大化・閉じるボタンが表示されない
- タイトルバーも消えている
- Ctrl+W では普通に閉じられる
- PC再起動、1Passwordの終了・再起動、アンインストール → 再インストール、いずれも変化なし
## 原因
**ただフルスクリーンモードに入っていただけ。**
1Passwordコミュニティの投稿「[Lost window minimize buttons top rhc.](https://www.1password.community/discussions/1password/lost-window-minimize-buttons-top-rhc-/135157)」で全く同じ症状が報告されていて、コミュニティマネージャーの回答が「F11でフルスクリーンを切り替えてみて」だった。
## 解決手順
1. 1Passwordのウィンドウをクリックしてフォーカスを当てる
2. **F11** を押す
これでタイトルバーとボタン類が戻ってくる。ダメな場合は `Win + ↓`(ウィンドウの復元)でも通常ウィンドウ状態に戻せる。
### aozora — a Rust parser for Aozora Bunko notation
URL: https://www.sakashita.page/aozora-rust-parser-aozora-bunko-notation/
Last updated: 2026-05-04T02:00:57.000Z
`aozora` is a Rust parser for **Aozora Bunko notation** (青空文庫記法), the in-text annotation language used in `.txt` files distributed by [Aozora Bunko](https://www.aozora.gr.jp/). It does not handle CommonMark or any Markdown dialect — only the Aozora Bunko notation itself. The implementation ships as five distribution surfaces: a CLI binary, a Rust library, a WASM module, a C ABI driver, and Python bindings via PyO3 / maturin.
```rust
use aozora::Document;
let source = "|青梅《おうめ》".to_owned();
let doc = Document::new(source);
let tree = doc.parse();
let html: String = tree.to_html();
let canonical: String = tree.serialize();
let diagnostics = tree.diagnostics();
assert_eq!(canonical, "|青梅《おうめ》");
```
- Handbook:
- Repository:
- Current release: v0.3.0, Rust 1.95 minimum, dual-licensed Apache-2.0 OR MIT
## What Aozora Bunko is
Aozora Bunko (literally "Open-Sky Library") is a volunteer-run digital library of Japanese-language literature in the public domain. It has been operating since 1997 and currently distributes more than 18,000 works — Sōseki, Akutagawa, Dazai, and several thousand other authors whose copyrights have expired under Japanese law. Volunteers transcribe printed editions into plain text, proofread them, and post the results for free download.
It is not a government agency or a commercial publisher. There is no curation editorial committee. The closest English-speaking analogue is Project Gutenberg, with one important difference: Aozora Bunko texts use a domain-specific in-line annotation syntax to encode features that plain text cannot natively represent — ruby (furigana), emphasis dots, vertical/horizontal text orientation, and references to non-Unicode Japanese characters. Without that annotation language, a great deal of typographic information from the print edition would be lost.
## What the notation looks like
A few examples of the notation Aozora Bunko texts contain, all of which `aozora` parses:
- **Ruby**: `|青梅《おうめ》` — phonetic gloss attached to a base text run (the kanji 青梅, which is a place name, gets the reading "おうめ")
- **Bouten** (emphasis dots): `[#「ここに傍点」に傍点]` — the Japanese equivalent of italics, rendered as dots above each character in vertical text
- **Tate-chu-yoko**: `[#「23」は縦中横]` — short horizontal runs inside vertical text (used for two-digit numbers)
- **Gaiji** (out-of-character-set references): `※[#「魚+師」、第3水準1-94-37]` — references to characters outside basic Japanese encodings, often via JIS X 0213 plane-row-cell coordinates
- **Kunten / kaeriten**: classical Chinese reading marks
- **Indent containers**: `[#ここから2字下げ]… [#ここで字下げ終わり]` — block-scoped indentation
- **Page and section breaks**: chapter delimiters
`aozora` recognises every annotation that appears in a real Aozora Bunko `.txt` source and converts it into a structured AST plus a stream of diagnostics for malformed input.
## Installation and use
Pre-built CLI binaries for Linux x86\_64, macOS arm64, and Windows x86\_64 are attached to every GitHub Release as `aozora-vX.Y.Z-.{tar.gz,zip}` archives, with `SHA256SUMS` alongside.
To build from source:
```sh
cargo install --git https://github.com/P4suta/aozora --locked aozora-cli
```
CLI subcommands cover the common tasks:
```sh
aozora check FILE.txt # lex and report diagnostics
aozora fmt --check FILE.txt # parse-then-serialize round-trip check
aozora render FILE.txt # render to HTML on stdout
aozora check -E sjis FILE.txt # read Shift_JIS, the Aozora Bunko native encoding
```
All subcommands accept `-` (or no path) to read from stdin.
The Rust library entry point is the snippet at the top of this article. `Document` owns a `bumpalo` arena; `tree` borrows from it for the lifetime of the `Document`. WASM, C ABI, and Python bindings have their own minimal examples in the handbook's Bindings chapters.
## What is distinctive about the implementation
Three design decisions are worth highlighting. Each has its own dedicated chapter in the handbook's Architecture section.
**Markdown is out of scope.** Aozora Bunko notation is not CommonMark, GFM, or any other Markdown dialect — it is a separate annotation system designed for transcribing print Japanese literature. `aozora` confines itself to that notation. If you want to write Markdown text that *also* contains Aozora Bunko annotations, the sibling project [afm](https://github.com/P4suta/afm) is a Markdown dialect built on top of `aozora`.
**SIMD multi-pattern scanner.** Aozora Bunko text is mostly UTF-8 Japanese (3 bytes per codepoint), with the seven trigger bytes (`|《》※[]` and full-width space) appearing roughly once per kilobyte of source. Scanning that sparse pattern set efficiently is the dominant cost in lexing, so `aozora` uses Intel Hyperscan's Teddy algorithm via `aho-corasick`. On x86\_64 with AVX2 it runs at \~12 GB/s on the corpus benchmark. On targets without AVX2 it falls back via runtime dispatch to a Hoehrmann-style multi-pattern DFA (\~3.5 GB/s). The wasm32 target uses `memchr`'s portable multi-pattern path (\~1.2 GB/s) until the WebAssembly SIMD proposal stabilises.
**Borrowed-arena AST.** A typical Aozora Bunko work parses to roughly 50,000 AST nodes. Allocating each one as an individual `Box` makes the allocator the bottleneck. `aozora` uses a single `bumpalo::Bump` arena per `Document`; the parser writes nodes into it in lex order and the tree borrows from the arena for its entire lifetime. On a corpus sweep over the full Aozora Bunko archive the arena variant is 6.4× faster than the equivalent `Box` shape, with peak RSS reduced by 30%. Drop is one `Bump::reset` call regardless of node count.
## Where to go next
- **Handbook**: — notation reference, architecture (borrowed-arena AST, SIMD scanner backends, Shift\_JIS + gaiji resolver, Eytzinger-layout sorted-set lookup), performance (PGO pipeline, samply workflow, corpus sweep), and bindings.
- **API reference**: — auto-deployed rustdoc.
- **Related projects**:
- [P4suta/afm](https://github.com/P4suta/afm) — CommonMark + GFM + Aozora Bunko notation, a unified Markdown dialect built on top of `aozora`.
- [P4suta/aozora-tools](https://github.com/P4suta/aozora-tools) — authoring tools: formatter, LSP server, tree-sitter grammar, VS Code extension.
Publication to crates.io is gated on the v1.0 API freeze. Until then, depend on a tagged commit (the install chapter of the handbook keeps the current `Cargo.toml` snippet).
### 外字と訓点を compile-time hash で解く
URL: https://www.sakashita.page/aozora-gaiji-phf-compile-time-hash/
Last updated: 2026-05-04T01:00:31.000Z
[aozora](https://github.com/P4suta/aozora) は青空文庫の外字参照 (`※[#「魚+師」、第3水準1-94-37]` のような形) を約 14,000 件のテーブルで解決する。このテーブルを runtime の `HashMap` ではなく `phf` (perfect hash function) で持ち、コンパイル時に static 配列に焼き込んでいる。この記事はその選択の根拠と、JIS X 0213 → Unicode フォールバックの設計をまとめたもの。
handbook の対応章: [Shift\_JIS + 外字 resolver](https://p4suta.github.io/aozora/arch/encoding.html)。
## 外字テーブルの形
外字エントリには 3 種類の解決結果があり、それぞれに対応する variant を `GaijiEntry` に持たせている。
```rust
static GAIJI_TABLE: phf::Map<&'static str, GaijiEntry> = phf_map! {
"1-94-37" => GaijiEntry::JisX0213 { plane: 1, row: 94, cell: 37, codepoint: '⿰魚師' },
"U+5F85" => GaijiEntry::Direct { codepoint: '待' },
"魚+師のつくり" => GaijiEntry::Description { fallback: "[魚+師]" },
// … 約 14,000 件
};
```
`phf` は `phf_codegen` クレートでビルド時に最小完全ハッシュを計算してくれる。実体は `static` な配列 + ハッシュ関数で、
- ルックアップは wyhash 1 ラウンド + 配列 1 添字 + strcmp 1 回
- ベンチハーネス計測で 1 件あたり約 25 ns
- collision のない perfect hash なので probe ループが無い
- アロケーションが無い
## 候補 1: `OnceLock`
「初回アクセスで `HashMap` を構築して以降キャッシュする」のは Rust では一般的なパターン。これを 14,000 件の外字テーブルに適用すると、
- 初回コスト: 14,000 件の `HashMap<&str, GaijiEntry>` 構築に約 5ms。これは小さい青空文庫作品のパース全体より長い
- メモリ: load factor 余裕と `RawTable` メタデータで PHF 比 2–3 倍
- 同期コスト: `OnceLock` は初期化後も毎回 atomic load が走る
外字解決は Phase 0 (sanitize) から Phase 1 (scan) の間で大量に呼ばれるホットパスなので、初期化コストもアトミック load も乗せたくない。
## 候補 2: JSON / TOML 等の外部アセット
「テーブルだけ別ファイルに分離して起動時にロード」も選択肢としてはある。これも採らなかった。
- `Document::new` ごとにファイル I/O が走るとパース全体の時間予算を食う
- 全バインディング (CLI、WASM、FFI、Python wheel) でアセットを別ファイル配布する必要が出てくる
- リンカが「実際に参照されない外字」を dead code elimination できなくなる
## なぜ PHF が向くか
外字テーブルは次の特性を全部満たしている。
1. **大きい**: 14,000 件は線形走査や Eytzinger 二分探索でも無視できない
2. **静的**: 内容はコンパイル時に確定する (青空文庫の仕様スナップショット由来)
3. **完全に既知**: 入力候補が全部書き出せるので、最小完全ハッシュが計算可能
これは PHF が想定している regime そのもの。ビルド時に `phf_codegen` が約 40 秒かけてハッシュ関数を構築し、生成物は `static` 配列として `.rodata` に置かれる。incremental ビルドではキャッシュされるので 2 回目以降は 0 秒。
## 解決の優先順序
外字参照の解決は 3 段階に分かれている。
```rust
pub fn resolve(reference: &str) -> Resolved {
// 1. 直接 Unicode 指定 (U+XXXX)
if let Some(c) = parse_unicode_form(reference) {
return Resolved::Direct(c);
}
// 2. JIS X 0213 plane-row-cell triple
if let Some(triple) = parse_jis_triple(reference) {
if let Some(c) = JIS_TABLE.get(&triple) {
return Resolved::Lookup(c);
}
}
// 3. 名前ベースの記述 (curated subset)
if let Some(fallback) = DESCRIPTION_TABLE.get(reference) {
return Resolved::Fallback(fallback);
}
Resolved::Unknown
}
```
`Direct` が最優先なのは、原稿執筆者が明示的に Unicode codepoint を書いた場合に JIS テーブル側の解釈で上書きしてはならないため。`Lookup` が一般的なケース、`Fallback` は Unicode に対応する文字が存在しない約 120 字向けの説明文出力 (例: `[魚+師]`)。どれにも当てはまらない参照は `Unknown` を返し、診断 `W0006` が立つ。
## アクセント分解 (おまけ)
古い青空文庫作品はアクセント付きラテン文字を `※[#…]` ではなく独自の ASCII 表記で書いていた時期がある。
```
M[i!]cher → Micher
M[a!]ria → Maria
[ae]on → Aeon
```
仕様の全 114 件マッピングは Phase 0 (sanitize) で適用され、Phase 1 のトリガースキャンに到達する時点では Unicode 化済み。こちらはエントリ数が少ないので PHF ではなく Eytzinger 配置の sorted-set を使っている (このサイズ域では Eytzinger の方が PHF よりキャッシュ効率が出る)。
## 整理
外字テーブルに PHF を選んだ理由は、
- テーブルが大きく (14,000 件)、静的で、完全に既知
- ホットパスで呼ばれるので初期化コストやアトミック load を排除したい
- 全バインディングが追加アセット無しで動く配布形態を保ちたい
ビルド時に 40 秒払うことで、runtime 側はゼロアロケーション・1 ハッシュ・1 strcmp の 25ns ルックアップになる。
実装は [aozora-encoding](https://github.com/P4suta/aozora/tree/main/crates/aozora-encoding) クレートにある。handbook の [encoding.md](https://p4suta.github.io/aozora/arch/encoding.html) には Shift\_JIS の `encoding_rs` 依存と JIS X 0213 plane-2 patch の話、それから decode chain 全体の図も載っている。
### 青空文庫の .txt を HTML に変換する最短手順
URL: https://www.sakashita.page/aozora-txt-to-html-quickstart/
Last updated: 2026-05-04T00:00:36.000Z
[青空文庫](https://www.aozora.gr.jp/) で配布されている `.txt` ファイルを HTML に変換したい、という用途向けの手順。Rust の知識は要らない。コマンド 1 行で済む。
## 1\. CLI バイナリを取ってくる
[aozora の Releases ページ](https://github.com/P4suta/aozora/releases) から自分の OS 向けのアーカイブを落とす。
| OS | アーカイブ名 |
| --------------- | ---------------------------------------------- |
| Linux x86\_64 | aozora-vX.Y.Z-x86\_64-unknown-linux-gnu.tar.gz |
| macOS arm64 | aozora-vX.Y.Z-aarch64-apple-darwin.tar.gz |
| Windows x86\_64 | aozora-vX.Y.Z-x86\_64-pc-windows-msvc.zip |
`SHA256SUMS` も同梱されているので、必要なら `shasum -a 256 -c SHA256SUMS` で検証できる。
展開すると `aozora` (Windows なら `aozora.exe`) というバイナリが出てくる。これを PATH の通った場所に置くか、その場で実行する。
## 2\. 変換する
青空文庫の `.txt` は Shift\_JIS なので、`-E sjis` を付ける (これを付けないと UTF-8 として読まれて文字化けする)。
```bash
aozora render -E sjis kokoro.txt > kokoro.html
```
これで `kokoro.html` ができる。標準出力に流すので、ファイル名は自由に決められる。
ファイルの代わりに標準入力から流すこともできる。
```bash
cat kokoro.txt | aozora render -E sjis - > kokoro.html
```
事前に書式が正しいか確かめたい場合は `check` サブコマンドを使う (HTML は出力されず、警告だけが表示される)。
```bash
aozora check -E sjis kokoro.txt
```
## 3\. 出てくる HTML
入力:
```
|青梅《おうめ》の山に、[#「ふと」に傍点]気が向いた。
```
出力 (抜粋):
```html
青梅の山に、ふと気が向いた。
```
ルビは HTML5 の `` `