1
1
# Ders 13: Macros
2
2
3
- Makrolar meta programlamanın temel unsurlarındadır. Meta programlama kod yazan kodlar oluşturulmasını benimseyen bir
4
- yaklaşımdır. Makroları kullanarak kod tekrarını azaltabilir ve daha okunabilir kodların oluşturulmasını sağlayabiliriz.
5
- Makrolar ile delerme zamanında kaynak kod üzerinde manipülasyon da yapılabilir. Ayrıca struct, enum, fn gibi yapıların
6
- derleme zamanında analiz edilip yeni kodların üretilmesini de sağlayabiliriz. Bugüne kadarki örneklerimizde bir çok
7
- makro kullandık. println!, write!, vec! gibi sonu ! ile biten enstrümanlar aslında birer makrodur. Makroları Declarative
8
- _ (Bildirime dayalı)_ ve Procedural _ (Yönergelere dayalı) _ olmak üzere iki ana kategoriye ayırabiliriz .
3
+ Makrolar kod yazan kodlar oluşturulmasını benimseyen bir meta programlama yaklaşımdır. Makroları kullanarak kod
4
+ tekrarlarını azaltabilir, daha okunabilir kodlar oluşturulabilir, delerme zamanında kaynak kod üzerinde değişikliler
5
+ yapılabilir. Ayrıca ** struct** , ** enum** , ** fn ** gibi yapıların derleme zamanında analiz edilip yeni kodların üretilmesi
6
+ de sağlanabilir. Önceki örneklerde bir çok makro kullanılmıştır. ** println! ** , ** write! ** , ** vec! ** gibi sonu ** ! ** ile
7
+ biten enstrümanlar aslında birer makrodur. Makrolar Declarative _ (Bildirime dayalı) _ ve Procedural _ (Yönergelere
8
+ dayalı)_ olmak üzere iki ana kategoriye ayrılır .
9
9
10
10
## Hello Macro _ (Declarative)_
11
11
12
- Makrolar belli bir fonksiyonelliğin farklı türevlerinin yazmaktan kaçınmamızı da sağlar . Aşağıdaki basit fonksiyonu göz
12
+ Makrolar ile belli bir fonksiyonelliğin farklı türevlerinin yazmak zorunda kalmayız . Aşağıdaki basit fonksiyonu göz
13
13
önüne alalım.
14
14
15
15
``` rust
@@ -32,10 +32,9 @@ mod tests {
32
32
}
33
33
```
34
34
35
- max_of_two fonksiyonu iki integer değerden hangisi büyükse onu geriye döndürmek amacıyla tasarlanmıştır. Ancak n adet
36
- sayının birbiriyle karşılaştırılmasını istediğimiz bir durumda ne yaparız? Bu genellikle yeni bir fonksiyon yazılmasını
37
- gerektirecektir. Bunun yerine bir makro hazırlayıp, sum_of fonksiyon bloğunun gelen koşullara göre üretilmesini de
38
- sağlayabiliriz.
35
+ ** max_of_two** fonksiyonu iki tam sayı değerden hangisi büyükse onu geriye döndürmek amacıyla tasarlanmıştır. Ancak n
36
+ adet sayının birbiriyle karşılaştırılması istendiğinde fonksiyonun farkı bir versiyonunun yazılması gerekir. Bunun
37
+ yerine bir makro hazırlayıp, ** max_of** fonksiyon bloğunun gelen koşullara göre üretilmesini sağlanabilir.
39
38
40
39
``` rust
41
40
macro_rules! max_of {
@@ -66,21 +65,22 @@ mod tests {
66
65
}
67
66
```
68
67
69
- Declarative yani bildirime dayalı makrolar yazmak için macro_rules isimli başka bir makro kullanılır. Kendi yazdığımız
70
- makrolar dahil isimlendirmelerde fonksiyon adı ! işareti ile sonlandırılır. Örnekte bazı özel kalıpları kullanılmıştır.
68
+ Declarative yani bildirime dayalı makrolar yazmak için ** macro_rules** isimli başka bir makro kullanılır. Kendi
69
+ yazdığımız makrolar dahil isimlendirmelerde fonksiyon adı ** !** işareti ile sonlandırılır. Örnekte bazı özel kalıplar
70
+ kullanılmıştır.
71
71
72
- - ($x: expr ) => { $x } : Tek bir argüman ile çalışılacağını belirtir. expr ile bir expression türü ifade edilir.
72
+ - ($x: expr ) => { $x } : Tek bir argüman ile çalışılacağını belirtir. ** expr** ile bir ** expression** türü ifade edilir.
73
73
Dolayısıyla makronun uygulandığı yapıda tek bir sayısal ifade varsa doğrudan döndüren bir kod bloğu üretilecektir.
74
- - ($x: expr , $y: expr ) => { ... } : Bu kısımda ise iki argümanla eşleşilen durumu ele alır. Burada if kullanılan bir kod
74
+ - ($x: expr , $y: expr ) => { ... } : Bu kısımda ise iki argümanla eşleşilen durum ele alınır. ** if ** kullanılan bir kod
75
75
bloğunun üretimi sağlanır.
76
76
- ($x: expr , $($y: expr ),+) => { ... } : Bu kalıpsa iki veya daha fazla argüman için geçerlidir. İkinci söz diziminde yer
77
- alan + operaötörü en az bir veya daha fazla anlamındadır . Bu durumla karşılaşılması halinde recursive olarak kendisini
78
- çağıran bir fonksiyon kodu üretilecektir .
77
+ alan ** + ** operaötörü en az bir veya daha fazla anlamına gelir . Bu durumla karşılaşılması halinde ** recursive** olarak
78
+ kendisini çağıran bir fonksiyon kodu üretilir .
79
79
80
80
## MetaSyntactic Variables
81
81
82
- Makrolarda ifadeleri analiz etmek ve eşleşmeleri yakalamak için token'lar kullanılır. Bunlardan en çok kullanılanlar
83
- aşağıdaki tabloda belirtilmektedir .
82
+ Makrolarda ifadeleri analiz etmek ve eşleşmeleri yakalamak için ** token** 'lar kullanılır. En sık kullanılan token
83
+ değerleri aşağıdaki tabloda özetlenmiştir .
84
84
85
85
| Token | Açıklama | Örnek |
86
86
| -----------| -----------------------------------------------------------------------| ---------------------------------------------------|
@@ -97,8 +97,8 @@ aşağıdaki tabloda belirtilmektedir.
97
97
98
98
## Örnekler
99
99
100
- Aşağıdaki kod parçalarında farklı senaryoların ele alındığı procedural makrolar yer almaktadır. İlk örnek bir model
101
- nesnesi için gerekli struct'ı kolayca oluşturmak için kullanılır.
100
+ Aşağıdaki kod parçalarında farklı senaryoların ele alındığı ** procedural** makro örnekleri yer almaktadır. İlk örnek bir
101
+ model nesnesi için gerekli struct'ın kolayca oluşturulması için kullanılır.
102
102
103
103
``` rust
104
104
macro_rules! crud {
@@ -139,12 +139,13 @@ mod tests {
139
139
}
140
140
```
141
141
142
- Örneğin Product, Customer, Order, Category ve benzeri entity nesnelerinin yer aldığı bir senaryoda her birisi için ayrı
143
- ayrı struct geliştirmek yerine bir makro ile kod tekrarlarının önüne geçebilir, veri yapılarını basitçe
144
- tanımlayabiliriz. crud isimli makro argüman olarak gelen identifier ve type bilgilerini kullanarak struct'ın temel
145
- halini inşa eder ve aynı zamanda new metodunu otomatik olarak implemente eder.
142
+ Örneğin ** Product** , ** Customer** , ** Order** , ** Category** ve benzeri entity nesnelerinin yer aldığı bir senaryoda her
143
+ biri için ayrı ayrı ** struct** yazmak yerine bir makro ile kod tekrarlarının önüne geçebilir. ** crud** isimli makro
144
+ argüman olarak gelen ** identifier** ve ** type** bilgilerini kullanarak ** struct** 'ın temel halini inşa eder ve aynı
145
+ zamanda ** new** metodunu da otomatik olarak implemente eder. Bu senaryo veri model nesnelerinin farklı kaynaklardan
146
+ geldiği _ (örneğin metin tabanlı dosyalar)_ durumlarda kullanışlı olabilir. Bunu araştırıp deneyiniz.
146
147
147
- Sıradaki örnek makro bir kod bloğunun çalışma süresini ölçümlemekte kullanılır.
148
+ Sıradaki makro bir kod bloğunun çalışma süresini ölçümlemek için kullanılır.
148
149
149
150
``` rust
150
151
macro_rules! wt {
@@ -175,11 +176,11 @@ mod tests {
175
176
}
176
177
```
177
178
178
- Örnekteki makro $block ifadesi ile aslında bir kod bloğunu ele alır. Bu bloğun öncesine bir sayaç yerleştirir ve son
179
- olarak da çıktıyı terminal ekranına basar. println! kullanımı demo ve öğrenim senaryoları için yeterlidir ancak makronun
180
- bir kütüphane üzerinden kullanıma açışması söz konusu olacaksa terminal bağımsız çalışan kısacası stdout üzerinden çıktı
181
- veren bir hale getirilmesi daha doğru olur . Bu, makronun biraz daha farklı yazılmasını gerektirebilir. Aşağıdaki kod
182
- parçasında bu durum ele alınmaktadır.
179
+ Örnekteki makro ** $block** ifadesi ile aslında bir kod bloğunu ele alır. Bu bloğun öncesine bir sayaç yerleştirir ve son
180
+ olarak da çıktıyı terminal ekranına basar. ** println!** kullanımı demo ve öğrenim senaryoları için yeterlidir ancak
181
+ makronun bir kütüphane üzerinden kullanıma açışması söz konusu ise terminal bağımsız çalışan kısacası ** stdout**
182
+ üzerinden çıktı veren bir hale getirilmesi daha doğrudur . Bu, makronun biraz daha farklı yazılmasını gerektirebilir.
183
+ Aşağıdaki kod parçasında bu durum ele alınmaktadır.
183
184
184
185
``` rust
185
186
macro_rules! wt_with {
@@ -211,17 +212,19 @@ mod tests {
211
212
}
212
213
```
213
214
214
- Makroya parametre atandığına dikkat edilmelidir. Bu bir nevi writeln! çağrısının hangi ortama yapılacağının
215
- soyutlanmasıdır. Test metodundaki gibi stdout verilmesi, bilginin terminaldeki test çıktısına yansıtılmasını sağlar.
215
+ Makroya parametre atandığına dikkat edilmelidir. Bu, bir nevi ** writeln!** çağrısının hangi ortama yapılacağının
216
+ soyutlanmasıdır. ** Test** metodundaki gibi ** stdout** verilmesi, bilginin terminaldeki test çıktısına yansıtılmasını
217
+ sağlar.
216
218
217
219
![ Macro Test Result] ( MacroTestResult.png )
218
220
219
- Dolayısıyla çıktının writeln! makrosunu kullanabilen bir logger'a, network stream'a veya bir veritabanına aktarılması da
220
- mümkündür. _ (Bu durum C#, Java gibi dillerdeki bileşen bağımlılıklarının metotlar üzerinden enjekte edilerek
221
- kullanılmasına da benzetilebilir. Daha detaylı bilgi için Dependency Injection konusuna bakılabilir)_
221
+ Dolayısıyla çıktının ** writeln!** makrosunu kullanabilen bir ** logger** nesnesine, ** network** stream'a veya bir
222
+ veritabanına aktarılması da mümkündür. _ (Bu durum C#, Java gibi dillerdeki bileşen bağımlılıklarının metotlar üzerinden
223
+ enjekte edilerek kullanılmasına da benzetilebilir. Daha detaylı bilgi için ** Dependency Injection** konusuna
224
+ bakılabilir)_
222
225
223
- Devam eden örnekte ise kod bloğu içerisinde gönderilen bir veri yapısının XML çıktısını hazırlayan kodların yazıldığı
224
- bir makro söz konusudur.
226
+ Devam eden örnekte ise kod bloğu içerisinde gönderilen bir veri yapısının ** XML** çıktısını hazırlayan kodların
227
+ yazıldığı bir makro söz konusudur.
225
228
226
229
``` rust
227
230
macro_rules! wt_with {
@@ -258,13 +261,15 @@ mod tests {
258
261
259
262
## Procedural Macros
260
263
261
- Procedural makrolar bir Rust kodundan yararlanarak başka bir rust kodu üretilmesinde sıklıkla kullanılır. TokenStream
262
- girdileri ile çalışır. Temelde üç türü vardır. Derive direktifi ile kullanılanlar, attribute olarak kullanılanlar ve
263
- fonksiyon stilinde kullanılanlar.
264
+ ** Procedural** makrolar bir Rust kodundan yararlanarak başka bir rust kodu üretilmesinde sıklıkla kullanılır.
265
+ ** TokenStream** girdileri ile çalışır. Bu nedenle dilin genel semantik yapısına ve abstract syntax tree gibi kavramlara
266
+ aşina olmakta yarar vardır. Temelde üç tür ** procedural** makro vardır. ** Derive** direktifi ile kullanılanlar,
267
+ ** attribute** şeklinde tasarlananlar ve fonksiyon stilinde yazılanlar.
264
268
265
269
![ Procedural Macro Types] ( ProceduralMacros.png )
266
270
267
- Declarative makrolar ile aralarında bazı farklılıklar da vardır. Bunlar aşağıdaki tabloda özetlenmiştir.
271
+ ** Procedural** ve ** Declarative** makrolar arasında bazı temel farklılıklar vardır. Bunlar aşağıdaki tabloda
272
+ özetlenmiştir.
268
273
269
274
| Özellik | Declarative Macros (` macro_rules! ` ) | Procedural Macros |
270
275
| --------------------------| --------------------------------------------------------| ---------------------------------------------------------------|
@@ -275,9 +280,9 @@ Declarative makrolar ile aralarında bazı farklılıklar da vardır. Bunlar aş
275
280
| ** Karmaşıklık Yönetimi** | Büyük ve karmaşık işleri yönetmek zordur | Büyük projelerde karmaşıklığın daha iyi yönetilmesini sağlar |
276
281
| ** Kapsam** | Kod tekrarını azaltma veya basit DSL'ler için idealdir | Gelişmiş DSL'ler, derive ve attribute işlevleri için idealdir |
277
282
278
- Procedural Macro'lar, proc-macro crate olarak adlandırılan ayrı bir kütüphanede yazılırlar. Rust söz dizimi üzerinde
279
- TokenStream kullanılarak işlem yapılması bazı durumlarda zorlayıcı olabilir. syn ve quote gibi küfeler genellikle işi
280
- kolaylaştıran enstrümanlar içerirler.
283
+ Procedural makrolar, ** proc-macro** crate olarak adlandırılan ayrı bir kütüphane formatında yazılırlar. Rust söz dizimi
284
+ üzerinde ** TokenStream** kullanılarak işlem yapılması bazı durumlarda zorlayıcı olabilir. ** syn** ve ** quote** gibi
285
+ küfeler genellikle işi kolaylaştıran enstrümanlar içerirler.
281
286
282
287
## Örnek Procedural Macro
283
288
@@ -291,14 +296,15 @@ cargo add quote
291
296
cargo add syn -F full
292
297
```
293
298
294
- Ardından toml dosyasında bu küfenin bir procedural macro olarak ele alınması gerektiği bildirilir .
299
+ Ardından ** toml** dosyasında bu küfenin procedural bir macro kütüphanesi olarak ele alınması gerektiği belirtilir .
295
300
296
301
``` toml
297
302
[lib ]
298
303
proc-macro = true
299
304
```
300
305
301
- Aşağıda kodun çalışma zamanının ölçen bir işlevselliğin procedural macro olarak nasıl yazılabileceği örneklenmektedir.
306
+ Aşağıda kodun çalışma zamanını ölçen bir işlevselliğin ** procedural macro** olarak nasıl yazılabileceği
307
+ örneklenmektedir.
302
308
303
309
``` rust
304
310
extern crate proc_macro;
@@ -325,15 +331,16 @@ pub fn work_time_effort(_attr: TokenStream, item: TokenStream) -> TokenStream {
325
331
}
326
332
```
327
333
328
- İlgili makro parametre olarak TokenStream'ler alır. Özellikle item değişkeni kod içerisinde kullanılır. item değişkeni
329
- ile gelen TokenStream parse_macro_input! makrosu kullanılarak içeriği ele alınabilir bir türe dönüştürülür. Buradan
330
- hareketle makronun uygulandığı metodun adı ve gövdesi yakalanbilir.
334
+ İlgili makro parametre olarak ** TokenStream** 'ler alır. Özellikle ** item** değişkeni kod içerisinde kullanılır. item
335
+ değişkeni ile gelen ** TokenStream** , ** parse_macro_input!** makrosu kullanılarak içeriği ele alınabilir bir türe
336
+ dönüştürülür. Bir başka deyişle stream üzerinden gelen kod içerisindeki unsurlar özellik şeklinde ele alınabilir hale
337
+ gelir. Buradan hareketle makronun uygulandığı metodun adı ve gövdesi yakalanmaktadır.
331
338
332
- İlerleyen adımda quote! makrosu ile yeni bir fonksiyon hazırlanır. Dikkat edileceği üzere gelen fonksiyon bloğunun
339
+ İlerleyen adımda ** quote!** makrosu ile yeni bir fonksiyon hazırlanır. Dikkat edileceği üzere gelen fonksiyon bloğunun
333
340
öncesine ve sonrasına yeni kod parçaları eklenmektedir. Makro çıktı olarak üretilen yeni kod parçasını yine bir
334
- TokenStream olarak dışarı verir.
341
+ ** TokenStream** olarak dışarı verir.
335
342
336
- Bu makro herhangibir metot için aşağıdaki gibi kullanılabilir.
343
+ Bu makro herhangi bir metot için aşağıdaki gibi kullanılabilir.
337
344
338
345
``` rust
339
346
mod samples ;
@@ -354,8 +361,8 @@ fn main() {
354
361
}
355
362
```
356
363
357
- Bir procedural macro küfesini kullanmak için ilgili projeye dependency olarak bildirilmesi gerekir. Bunun için toml
358
- dosyasındaki ilgili kısım değiştirilmelidir. Örneğin,
364
+ Bir procedural macro küfesini kullanmak için ilgili projeye ** dependency** olarak bildirilmesi gerekir. Bunun için makro
365
+ kütüphanesini kullanacak projenin ** toml ** dosyasında aşağıdaki gibi bir değişiklik yapılmalıdır.
359
366
360
367
``` toml
361
368
[dependencies ]
0 commit comments